¿Por qué los campos privados son privados para el tipo, no la instancia?

En C # (y en muchos otros idiomas) es perfectamente legítimo acceder a campos privados de otras instancias del mismo tipo. Por ejemplo:

public class Foo { private bool aBool; public void DoBar(Foo anotherFoo) { if(anotherFoo.aBool) ... } } 

Como la especificación C # (secciones 3.5.1, 3.5.2) indica que el acceso a los campos privados está en un tipo, no en una instancia. He estado discutiendo esto con un colega y estamos tratando de encontrar una razón por la cual funciona así (en lugar de restringir el acceso a la misma instancia).

El mejor argumento que podríamos proponer es para las comprobaciones de igualdad donde la clase puede querer acceder a los campos privados para determinar la igualdad con otra instancia. ¿Hay alguna otra razón? ¿O alguna razón dorada que absolutamente significa que debe funcionar así o algo sería completamente imposible?

    Creo que una razón por la que funciona de esta manera es porque los modificadores de acceso funcionan en tiempo de comstackción . Como tal, determinar si un objeto determinado es también el objeto actual no es fácil de hacer. Por ejemplo, considere este código:

     public class Foo { private int bar; public void Baz(Foo other) { other.bar = 2; } public void Boo() { Baz(this); } } 

    ¿Puede el comstackdor necesariamente darse cuenta de que el other es realmente this ? No en todos los casos. Se podría argumentar que esto simplemente no debería comstackrse entonces, pero eso significa que tenemos una ruta de código donde no se puede acceder a un miembro de instancia privada de la instancia correcta , lo cual creo que es aún peor.

    Solo requiere visibilidad de nivel de tipo en lugar de objeto para garantizar que el problema sea manejable, así como para que funcione una situación que parezca que debería funcionar.

    EDITAR : El punto de Danilel Hilgarth de que este razonamiento es al revés tiene sus méritos. Los diseñadores de idiomas pueden crear el idioma que desean y los escritores de comstackdores deben cumplirlo. Dicho esto, los diseñadores de idiomas tienen algún incentivo para facilitarles el trabajo a los escritores de comstackdores. (Aunque en este caso, es bastante fácil argumentar que solo se podría acceder a los miembros privados a través de this (implícita o explícitamente)).

    Sin embargo, creo que eso hace que el tema sea más confuso de lo necesario. La mayoría de los usuarios (yo incluido) encontrarían que es innecesariamente limitante si el código anterior no funcionó: ¡después de todo, esos son mis datos a los que bash acceder! ¿Por qué debería tener que pasar por this ?

    En resumen, creo que puedo haber exagerado el caso por ser “difícil” para el comstackdor. Lo que realmente quise transmitir es que la situación anterior parece ser una que a los diseñadores les gustaría tener.

    Debido a que el propósito del tipo de encapsulación utilizado en C # y en lenguajes similares * es disminuir la dependencia mutua de diferentes fragmentos de código (clases en C # y Java), no en objetos diferentes en la memoria.

    Por ejemplo, si escribe código en una clase que utiliza algunos campos en otra clase, estas clases están muy vinculadas. Sin embargo, si está tratando con código en el que tiene dos objetos de la misma clase, entonces no hay dependencia adicional. Una clase siempre depende de sí misma.

    Sin embargo, toda esta teoría sobre la encapsulación falla tan pronto como alguien crea propiedades (o obtiene / establece pares en Java) y expone todos los campos directamente, lo que hace que las clases estén acopladas como si estuvieran accediendo a campos de todos modos.

    * Para una aclaración sobre los tipos de encapsulación, ver la excelente respuesta de Abel.

    Ya se han agregado bastantes respuestas a este hilo interesante, sin embargo, no encontré la verdadera razón de por qué este comportamiento es así. Déjame intentarlo:

    De vuelta en los días

    En algún lugar entre Smalltalk en los 80 y Java a mediados de los 90, el concepto de orientación a objetos maduró. El ocultamiento de información, que originalmente no se pensó como un concepto disponible solo para OO (mencionado por primera vez en 1978), se introdujo en Smalltalk ya que todos los datos (campos) de una clase son privados, todos los métodos son públicos. Durante los muchos desarrollos nuevos de OO en la década de los 90, Bertrand Meyer intentó formalizar gran parte de los conceptos de OO en su histórico libro Object Oriented Software Construction (OOSC) que desde entonces se considera una referencia (casi) definitiva sobre los conceptos OO y el diseño del lenguaje .

    En el caso de la visibilidad privada

    Según Meyer, un método debe estar disponible para un conjunto definido de clases (página 192-193). Esto proporciona obviamente una alta granularidad de ocultación de información, la siguiente característica está disponible para classA y classB y todos sus descendientes:

     feature {classA, classB} methodName 

    En el caso de private , dice lo siguiente: sin declarar explícitamente un tipo como visible para su propia clase, no puede acceder a esa característica (método / campo) en una llamada calificada. Es decir, si x es una variable, x.doSomething() no está permitido. El acceso no calificado está permitido, por supuesto, dentro de la clase misma.

    En otras palabras: para permitir el acceso de una instancia de la misma clase, debe permitir el acceso al método explícitamente. Esto a veces se denomina instancia privada versus clase privada.

    Instancia-privada en lenguajes de progtwigción

    Sé de al menos dos idiomas actualmente en uso que usan ocultación de información privada de la instancia en oposición a la ocultación de información de clase privada. Uno es Eiffel, un lenguaje diseñado por Meyer, que lleva a OO a sus extremos extremos. El otro es Ruby, un lenguaje mucho más común hoy en día. En Ruby, private significa: “privado para esta instancia” .

    Opciones para el diseño del lenguaje

    Se ha sugerido que permitir instancias privadas sería difícil para el comstackdor. No lo creo, ya que es relativamente simple permitir o no permitir llamadas calificadas a los métodos. Si para un método privado, doSomething() está permitido y x.doSomething() no lo es, un diseñador de lenguaje ha definido de manera efectiva el acceso de instancia únicamente para los métodos y campos privados.

    Desde un punto de vista técnico, no hay ninguna razón para elegir de una forma u otra (especialmente cuando Eiffel.NET puede hacer esto con IL, incluso con herencia múltiple, no hay razón inherente para no proporcionar esta característica).

    Por supuesto, es una cuestión de gusto y, como ya se ha mencionado, algunos métodos pueden ser más difíciles de escribir sin la característica de visibilidad a nivel de clase de los métodos y campos privados.

    Por qué C # permite solo la encapsulación de clases y no la encapsulación de instancias

    Si observa los subprocesos de Internet en la encapsulación de instancias (un término que a veces se usa para referirse al hecho de que un lenguaje define los modificadores de acceso en el nivel de instancia, a diferencia del nivel de clase), el concepto a menudo es desaprobado. Sin embargo, teniendo en cuenta que algunos lenguajes modernos usan la encapsulación de instancias, al menos para el modificador de acceso privado, te hace pensar que puede ser y es útil en el mundo de la progtwigción moderna.

    Sin embargo, C # ciertamente se ha esforzado en C ++ y Java por su diseño de lenguaje. Mientras que Eiffel y Modula-3 también estuvieron en la imagen, considerando las muchas características de Eiffel que faltan (herencia múltiple), creo que eligieron la misma ruta que Java y C ++ cuando se trataba del modificador de acceso privado.

    Si realmente quieres saber por qué deberías intentar contactar a Eric Lippert, Krzysztof Cwalina, Anders Hejlsberg o cualquier otra persona que trabaje en el estándar de C #. Desafortunadamente, no pude encontrar una nota definitiva en el Lenguaje de progtwigción C # anotado.

    Esta es solo mi opinión, pero pragmáticamente, creo que si un progtwigdor tiene acceso a la fuente de una clase, puede confiar razonablemente en que accedan a los miembros privados de la instancia de la clase. ¿Por qué obligar a los progtwigdores a la mano derecha cuando en la izquierda ya les has dado las llaves del reino?

    La razón es, por supuesto, la verificación de igualdad, la comparación, la clonación, la sobrecarga del operador … Sería muy complicado implementar operador + en números complejos, por ejemplo.

    Antes que nada, ¿qué pasaría con los miembros estáticos privados? ¿Se puede acceder solo por métodos estáticos? Ciertamente no querrías eso, porque entonces no podrías acceder a tus instalaciones.

    En cuanto a su pregunta explícita, considere el caso de un StringBuilder , que se implementa como una lista vinculada de instancias de sí mismo:

     public class StringBuilder { private string chunk; private StringBuilder nextChunk; } 

    Si no puede acceder a los miembros privados de otras instancias de su propia clase, debe implementar ToString esta manera:

     public override string ToString() { return chunk + nextChunk.ToString(); } 

    Esto funcionará, pero es O (n ^ 2) – no muy eficiente. De hecho, eso probablemente derrota el propósito de tener una clase StringBuilder en primer lugar. Si puede acceder a los miembros privados de otras instancias de su propia clase, puede implementar ToString creando una cadena de la longitud adecuada y luego haciendo una copia insegura de cada fragmento al lugar apropiado en la cadena:

     public override string ToString() { string ret = string.FastAllocateString(Length); StringBuilder next = this; unsafe { fixed (char *dest = ret) while (next != null) { fixed (char *src = next.chunk) string.wstrcpy(dest, src, next.chunk.Length); next = next.nextChunk; } } return ret; } 

    Esta implementación es O (n), lo que hace que sea muy rápido, y solo es posible si tiene acceso a miembros privados de otras instancias de su clase .

    Esto es perfectamente legítimo en muchos idiomas (C ++ para uno). Los modificadores de acceso provienen del principio de encapsulación en OOP. La idea es restringir el acceso al exterior , en este caso afuera hay otras clases. Cualquier clase anidada en C #, por ejemplo, puede acceder también a sus padres miembros privados.

    Si bien esta es una opción de diseño para un diseñador de idiomas. La restricción de este acceso puede complicar extremadamente algunos escenarios muy comunes sin contribuir mucho al aislamiento de las entidades.

    Hay una discusión similar aquí

    No creo que haya una razón por la que no podamos agregar otro nivel de privacidad, donde los datos son privados para cada instancia. De hecho, eso podría incluso proporcionar una agradable sensación de integridad al lenguaje.

    Pero en la práctica, dudo que realmente sea tan útil. Como ha señalado, nuestra privacidad habitual es útil para cosas tales como las verificaciones de igualdad, así como para la mayoría de las otras operaciones que implican varias instancias de un Tipo. Aunque, también me gusta su punto de mantener la abstracción de datos, ya que es un punto importante en OOP.

    Creo que por todos lados, proporcionar la capacidad de restringir el acceso de esa manera podría ser una buena característica para agregar a OOP. ¿Es realmente tan útil? Yo diría que no, ya que una clase debería poder confiar en su propio código. Dado que esa clase es lo único que puede acceder a miembros privados, no hay una razón real para necesitar abstracción de datos cuando se trata de una instancia de otra clase.

    Por supuesto, siempre puedes escribir tu código como si se aplicara de forma privada a las instancias. Use los métodos usuales get/set para acceder / cambiar los datos. Eso probablemente haría que el código sea más manejable si la clase puede estar sujeta a cambios internos.

    Grandes respuestas dadas arriba. Yo agregaría que parte de este problema es el hecho de que la instanciación de una clase dentro de sí misma está permitida en primer lugar. Lo hace en un bucle “for” de lógica recursiva, por ejemplo, para usar ese tipo de truco siempre que tenga lógica para finalizar la recursión. Pero crear instancias o pasar la misma clase dentro de sí mismo sin crear tales bucles lógicamente crea sus propios peligros, a pesar de que es un paradigma de progtwigción ampliamente aceptado. Por ejemplo, una clase C # puede instanciar una copia de sí mismo en su constructor predeterminado, pero eso no rompe ninguna regla ni crea bucles causales. ¿Por qué?

    Por cierto … este mismo problema también se aplica a los miembros “protegidos”. 🙁

    Nunca acepté completamente ese paradigma de progtwigción porque aún viene con un conjunto completo de problemas y riesgos que la mayoría de los progtwigdores no captan completamente hasta que problemas como este surgen y confunden a las personas y desafían toda la razón para tener miembros privados.

    Este aspecto “extraño y absurdo” de C # es una razón más por la cual la buena progtwigción no tiene nada que ver con la experiencia y la habilidad, sino simplemente con conocer los trucos y las trampas … como trabajar en un automóvil. Es el argumento de que las reglas estaban destinadas a romperse, lo cual es un modelo muy malo para cualquier lenguaje de computación.

    Me parece que si los datos fueran privados para otras instancias del mismo tipo, ya no serían necesariamente del mismo tipo. No parece comportarse o actuar de la misma manera que otras instancias. El comportamiento podría modificarse fácilmente en función de los datos internos privados. Eso solo generaría confusión en mi opinión.

    En términos generales, personalmente creo que escribir clases derivadas de una clase base ofrece una funcionalidad similar a la que está describiendo con ‘tener datos privados por instancia’. En cambio, solo tiene una nueva definición de clase por tipo ‘único’.