tipos - ¿Por qué es necesario llamar: this() en una estructura para usar propiedades automáticas en c#?
tipos de clases programacion (2)
Una propiedad no es más que una encapsulación de un método Get
y / o un método Set
. El CLR tiene metadatos que indican que los métodos particulares deben considerarse como propiedades, lo que significa que los compiladores deberían permitir algunos constructos que no permitirían con los métodos. Por ejemplo, si X
es una propiedad de lectura y escritura de Foo
, un compilador traducirá Foo.X += 5
en Foo.SET_X_METHOD(Foo.GET_X_METHOD() + 5)
(aunque los métodos tienen un nombre diferente y no son generalmente accesibles) por nombre).
Aunque una propiedad automática implementa un par de métodos get / set que acceden a un campo privado de tal manera que se comportan más o menos como un campo, desde el punto de vista de cualquier código fuera de la propiedad, una propiedad automática es un par de get / establecer métodos como cualquier otra propiedad. En consecuencia, una declaración como Foo.X = 5;
se traduce como Foo.SET_X_METHOD(5)
. Como el compilador de C # solo ve eso como una llamada a método, y como los métodos no incluyen ningún metadato para indicar qué campos leen o escriben, el compilador prohibirá la llamada al método a menos que sepa que todos los campos de Foo
han sido escritos.
Personalmente, mi consejo sería evitar el uso de autopropiedades con tipos de estructuras. Las propiedades automáticas tienen sentido con las clases, ya que es posible que las propiedades de clase admitan características como las notificaciones de actualización. Incluso si las primeras versiones de una clase no son compatibles con las notificaciones de actualización, tener esas versiones usar una propiedad automática en lugar de un campo significa que las versiones futuras pueden agregar características de notificación de actualización sin requerir que los usuarios de la clase sean reelaborados. Las estructuras, sin embargo, no pueden admitir significativamente la mayoría de los tipos de características que uno podría desear agregar a las propiedades de campo.
Además, las diferencias de rendimiento entre los campos y las propiedades son mucho mayores con las estructuras grandes que con los tipos de clases. De hecho, gran parte de la recomendación de evitar grandes estructuras es una consecuencia de esta diferencia. Las estructuras grandes en realidad pueden ser muy eficientes, si uno evita copiarlas innecesariamente. Incluso si uno tuviera una estructura enorme HexDecet<HexDecet<HexDecet<Integer>>>
, donde HexDecet<T>
contenía campos expuestos F0
.. F15
de tipo T
, una declaración como Foo = MyThing.F3.F6.F9;
simplemente requeriría leer un entero de MyThing
y almacenarlo en Foo
, aunque MyThing
haría en gran medida por estándares struct (4096 enteros ocupando 16K). Además, uno podría actualizar ese elemento muy fácilmente, por ejemplo, MyThing.F3.F6.F9 += 26;
. Por el contrario, si F0
.. F15
fueran auto-propiedades, la declaración Foo = MyThing.F3.F6.F9
requeriría copiar 1K de datos de MyThing.F3
a un temporal (llámalo temp1
, luego 64 bytes de datos de temp1.F6
a temp2
) antes de finalmente llegar a leer 4 bytes de datos de temp2.F9
. Ick. Peor aún, intentar agregar 26 al valor en MyThing.F3.F6.F9
requeriría algo como var t1 = MyThing.F3; var t2 = t1.F6; t2.F9 += 26; t1.F6 = f2; MyThing.F3 = t1;
var t1 = MyThing.F3; var t2 = t1.F6; t2.F9 += 26; t1.F6 = f2; MyThing.F3 = t1;
.
Muchas de las quejas de larga data sobre "tipos de estructuras mutables" son realmente quejas sobre los tipos de estructuras con propiedades de lectura / escritura. Simplemente reemplace las propiedades con campos y los problemas desaparecerán.
PD: Hay ocasiones en que puede ser útil tener una estructura cuyas propiedades accedan a un objeto de clase al que tenga una referencia. Por ejemplo, sería bueno tener una versión de una ArraySegment<T>
que permitiera decir Var foo[] = new int[100]; Var MyArrSeg = New ArraySegment<int>(foo, 25, 25); MyArrSeg[6] += 9;
Var foo[] = new int[100]; Var MyArrSeg = New ArraySegment<int>(foo, 25, 25); MyArrSeg[6] += 9;
, y hacer que la última declaración agregue nueve al elemento (25 + 6) de foo
. En versiones anteriores de C # uno podía hacer eso. Desafortunadamente, el uso frecuente de autopropiedades en el Framework donde los campos hubieran sido más apropiados dio lugar a quejas generalizadas sobre el compilador que permite llamar inútilmente a los diseñadores de propiedades en estructuras de solo lectura; en consecuencia, ahora se prohíbe llamar a cualquier setter de propiedad en una estructura de solo lectura, independientemente de si el establecedor de propiedad modificaría o no los campos de la estructura. Si las personas simplemente se abstuvieran de hacer estructuras mutables a través de los establecedores de propiedades (haciendo que los campos sean accesibles directamente cuando la mutabilidad sea apropiada) los compiladores nunca habrían tenido que implementar esa restricción.
Si defino una estructura en C # usando propiedades automáticas como esta:
public struct Address
{
public Address(string line1, string line2, string city, string state, string zip)
{
Line1 = line1;
Line2 = line2;
City = city;
State = state;
Zip = zip;
}
public string Line1 { get; protected set; }
public string Line2 { get; protected set; }
public string City { get; protected set; }
public string State { get; protected set; }
public string Zip { get; protected set; }
}
Cuando intento construir el archivo, aparece un error de compilación que dice The ''this'' object cannot be used before all of its fields are assigned to
. Esto se puede resolver cambiando el constructor para hacer una llamada encadenada al constructor predeterminado de esta manera:
public Address(string line1, string line2, string city, string state, string zip): this()
{
Line1 = line1;
Line2 = line2;
City = city;
State = state;
Zip = zip;
}
Mi pregunta es, ¿por qué funciona esto y qué está sucediendo? Supongo, y traté de probarlo mirando a IL, pero solo me estoy engañando a mí mismo si creo que puedo derribar a IL. Pero mi conjetura es que las propiedades automáticas funcionan haciendo que el compilador genere campos para sus propiedades detrás de escena. No se puede acceder a esos campos a través del código, todos los ajustes y las obtenciones deben realizarse a través de las propiedades. Al crear una estructura, un constructor predeterminado no se puede definir explícitamente. Así que detrás de escena, el compilador debe estar generando un constructor predeterminado que establece los valores de los campos que el desarrollador no puede ver.
Todos y cada uno de los asistentes de IL pueden demostrar o refutar mi teoría.
Nota: a partir de C # 6, esto no es obligatorio, pero debería usar propiedades de solo lectura implementadas automáticamente con C # 6 de todos modos ...
this()
se asegura de que los campos estén definitivamente asignados en lo que respecta al compilador: establece todos los campos en sus valores predeterminados. Debe tener una estructura completamente construida antes de poder comenzar a acceder a cualquier propiedad.
Es molesto, pero así son las cosas. ¿Estás seguro de que realmente quieres que esto sea una estructura? ¿Y por qué usar un setter protegido en una estructura (de la que no se puede derivar)?