Noté un comportamiento extraño en mi código al comentar accidentalmente una línea en una función durante la revisión del código. Fue muy difícil de reproducir, pero aquí describiré un ejemplo similar.
Tengo esta clase de prueba:
public class Test
{
public void GetOut(out EmailAddress email)
{
try
{
Foo(email);
}
catch
{
}
}
public void Foo(EmailAddress email)
{
}
}
No hay ninguna asignación para correo electrónico en la GetOut
que normalmente arrojaría un error:
El parámetro de salida 'correo electrónico' debe asignarse antes de que el control abandone el método actual
Sin embargo, si EmailAddress está en una estructura en un ensamblaje separado, no se crea ningún error y todo se compila bien.
public struct EmailAddress
{
#region Constructors
public EmailAddress(string email)
: this(email, string.Empty)
{
}
public EmailAddress(string email, string name)
{
this.Email = email;
this.Name = name;
}
#endregion
#region Properties
public string Email { get; private set; }
public string Name { get; private set; }
#endregion
}
¿Por qué el compilador no exige que se le asigne el correo electrónico? ¿Por qué este código se compila si la estructura se crea en un ensamblaje separado, pero no se compila si la estructura se define en el ensamblaje existente?
struct Dog{}
, todo está bien.Respuestas:
TLDR: Este es un error conocido de larga data. Primero escribí sobre esto en 2010:
https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/
Es inofensivo y puede ignorarlo con seguridad, y felicitarse por encontrar un error algo oscuro.
Oh, lo hace, de una manera. Simplemente tiene una idea errónea de qué condición implica que la variable está definitivamente asignada, como veremos.
Ese es el quid de la falla. El error es una consecuencia de la intersección de cómo el compilador de C # realiza una verificación de asignación definitiva en las estructuras y cómo el compilador carga los metadatos de las bibliotecas.
Considera esto:
OK, en este punto, ¿qué sabemos?
f
es un alias para una variable de tipoFoo
, por lo que el almacenamiento ya se ha asignado y definitivamente está al menos en el estado en que salió del asignador de almacenamiento. Si la persona que llama colocó un valor en la variable, ese valor está allí.¿Qué requerimos? Requerimos que
f
se asigne definitivamente en cualquier punto donde el control salgaM
normalmente. Entonces esperarías algo como:que establece
f.x
yf.y
a sus valores predeterminados. ¿Pero qué hay de esto?Eso también debería estar bien. Pero, y aquí está el truco, ¿por qué necesitamos asignar los valores predeterminados solo para eliminarlos un momento después? ¡El comprobador de asignación definitiva de C # verifica si cada campo está asignado! Esto es legal:
¿Y por qué eso no debería ser legal? Es un tipo de valor.
f
es una variable y ya contiene un valor de tipo válidoFoo
, así que configuremos los campos y listo, ¿verdad?Derecha. Entonces, ¿cuál es el error?
El error que ha descubierto es: como ahorro de costos, el compilador de C # no carga los metadatos de los campos privados de estructuras que se encuentran en las bibliotecas a las que se hace referencia . Esos metadatos pueden ser enormes y ralentizaría el compilador para ganar muy poco para cargarlo todo en la memoria cada vez.
Y ahora debería poder deducir la causa del error que ha encontrado. Cuando el compilador verifica si el parámetro out está definitivamente asignado, compara el número de campos conocidos con el número de campos que se inicializaron definitivamente y, en su caso, solo conoce los cero campos públicos porque los metadatos del campo privado no se cargaron. . El compilador concluye "cero campos requeridos, cero campos inicializados, estamos bien".
Como dije, este error ha existido por más de una década y la gente como usted ocasionalmente lo redescubre y lo informa. Es inofensivo y es poco probable que se solucione porque solucionarlo es de beneficio casi nulo pero tiene un alto costo de rendimiento.
Y, por supuesto, el error no reproduce los campos privados de estructuras que están en el código fuente de su proyecto, porque obviamente el compilador ya tiene información sobre los campos privados disponibles.
fuente
System.TimeSpan
en su lugar, los errores vienen:error CS0269: Use of unassigned out parameter 'email'
yerror CS0177: The out parameter 'email' must be assigned to before control leaves the current method
. Solo hay un campo no estático deTimeSpan
, a saber_ticks
. Esinternal
a su asamblea mscorlib. ¿Es especial esta asamblea? Lo mismo conSystem.DateTime
, y su campo esprivate
Si bien parece un error, tiene sentido.
El 'error faltante' solo aparece cuando se usa una biblioteca de clases. Y una biblioteca de clase podría haber sido escrita en otro lenguaje .net, por ejemplo, VB.Net. El 'seguimiento de asignación definitiva' es una característica de C #, no del marco.
Entonces, en general, no creo que sea un error, pero no sé acerca de una declaración autorizada para esto.
fuente
default(T)
). Por lo tanto, no hay violación de la seguridad de la memoria o algo similar.