2012-09-21 16 views
6

En el siguiente código, paso una estructura a un constructor que está esperando una clase. ¿Por qué se compila y se ejecuta sin error (y produce el resultado deseado)?¿No se aplica "donde T: clase" de ninguna manera en tiempo de compilación o tiempo de ejecución?

class Program 
{ 
    static void Main() 
    { 
     var entity = new Foo { Id = 3 }; 
     var t = new Test<IEntity>(entity); // why doesn't this fail? 
     Console.WriteLine(t.Entity.Id.ToString()); 
     Console.ReadKey(); 
    } 
} 

public class Test<TEntity> where TEntity : class 
{ 
    public TEntity Entity { get; set; } 

    public Test(TEntity entity) 
    { 
     Entity = entity; 
    } 

    public void ClearEntity() 
    { 
     Entity = null; 
    } 
} 

public struct Foo : IEntity 
{ 
    public int Id { get; set; } 
} 

public interface IEntity 
{ 
    int Id { get; set; } 
} 

Si cambio mi método Main() de manera que incluya una llamada a ClearEntity(), como se muestra a continuación, que todavía genera ningún error. ¿Por qué?

static void Main() 
{ 
    var entity = new Foo { Id = 3 }; 
    var t = new Test<IEntity>(entity); 
    Console.WriteLine(t.Entity.Id.ToString()); 
    t.ClearEntity(); // why doesn't this fail? 
    Console.ReadKey(); 
} 
+5

'Foo' siendo' IEntity', estará en caja. La instancia en caja es una instancia de una clase. – Humberto

+0

¿Cómo puede compilar 't.Entity.Id' cuando' Entity' se define como type 'TEntity' donde' TEntity: class', que no implementa 'IEntity'? –

Respuesta

8

where TEntity : class fuerzas TEntity a ser un tipo de referencia, pero una interfaz como IEntity es un tipo de referencia.

Ver aquí: http://msdn.microsoft.com/en-us/library/d5x73970(v=vs.80).aspx

donde T: Clase | El argumento tipo debe ser un tipo de referencia, incluida cualquier clase, interfaz, delegado o tipo de matriz

En relación con su segunda pregunta, podría pensar que t.ClearEntity() fallaría porque está asignando nulo a una variable cuyo tipo es un tipo de valor, pero ese no es el caso. El tipo de tiempo de compilación Entity es el tipo de referencia IEntity, y el tipo de tiempo de ejecución (después de la asignación) es el tipo nulo. Por lo tanto, nunca tendrá una variable del tipo Foo, pero tendrá un valor de null.

+0

+1. Buena explicación, gracias. Esto abre algunas buenas posibilidades que no sabía que estaban disponibles. – devuxer

+1

Gracias, agregué un poco más sobre por qué 'ClearEntity' no falla. Pensé que valía la pena señalar que el tipo de tiempo de ejecución almacenado en 'Entity' se cambia al asignarle nulo. –

2

de la documentación de C#:

donde T: clase

El tipo de argumento debe ser un tipo de referencia, incluyendo cualquier clase, interfaz, delegado, o tipo de matriz. (Consulte la nota a continuación.)

Dado que está pasando la estructura a través de una interfaz, todavía se considera un tipo de referencia.

+0

Como está encasillado, en realidad es un tipo de referencia, no es como un tipo de valor que simplemente no está siendo filtrado por la restricción 'clase'. – Servy

1

Dentro del tiempo de ejecución .net, cada tipo de valor no admitible tiene un tipo de referencia asociado (a menudo denominado "tipo de valor encuadrado") que deriva de System.ValueType. Diciendo Object Foo = 5; en realidad no almacenará un Int32 en Foo; en cambio, creará una nueva instancia del tipo de referencia asociado con Int32 y almacenará una referencia a esa instancia. Una restricción class en un tipo genérico especifica que el tipo en cuestión debe ser algún tipo de tipo de referencia, pero no excluye por sí mismo la posibilidad de que el tipo pueda usarse para pasar una referencia a una instancia de tipo de valor encuadrado. En la mayoría de los contextos fuera de las restricciones de tipo genérico, los tipos de interfaz se consideran tipos de clase.

Es importante tener en cuenta que no solo se almacenan los tipos de valores encuadrados como tipos de referencia; se comportan como tipos de referencia. Por ejemplo, List<string>.Enumerator es un tipo de valor que implementa IEnumerator<string>. Si uno tiene dos variables de tipo List<string>.Enumerator, al copiar una una a la otra, se copiará el estado de la enumeración, de modo que habrá dos enumeradores separados e independientes que apunten a la misma lista. Copiando una de esas variables a una variable del tipo IEnumerator<string> creará una nueva instancia del tipo de valor encuadrado asociado con List<string.Enumerator y almacenará en la última variable una referencia a ese nuevo objeto (que será un tercer enumerador independiente).Sin embargo, al copiar esa variable en otra de tipo IEnumerator<string>, simplemente se almacenará una referencia al objeto existente (ya que IEnumerator<string> es un tipo de referencia).

El lenguaje C# intenta simular que los tipos de valores derivan de Object, pero que en realidad no funcionan en las entrañas de .net Runtime. En cambio, son convertibles a tipos que derivan de System.ValueType (que a su vez deriva de Object). Los últimos tipos satisfarán una restricción de tipo, aunque los anteriores no lo harán. Por cierto, a pesar de su nombre, System.ValueType es en realidad un tipo de clase.

0

Igualmente, asumí que la palabra clave de restricción class significaba la misma clase que la palabra clave de declaración de tipo class, pero no es así.

Como se explica en las otras respuestas, el término class aquí está sobrecargado, lo que me parece una decisión horrible para el diseño del lenguaje C#. Algo como referencetype habría sido más útil.

+0

Un problema más fundamental es la ficción de que los lugares de almacenamiento de tipo de valor contienen cosas que se derivan de 'Objeto'. Los tipos de valores encuadrados se derivan de 'Objeto', pero las ubicaciones de almacenamiento de tipo valor no los tienen. Fundamentalmente, una restricción de 'clase' en 'T' implica dos cosas, como p. Ej. 'T foo;': (1) el valor predeterminado de 'foo' será una referencia nula; (2) 'foo.bar()' invocará el método 'bar()' para actuar sobre una 'T' a la cual' foo' contiene una referencia, sin dar acceso a ese método a 'foo'. Esas cosas no son menos ciertas en los tipos de valores en caja que en cualquier otro tipo de clase. – supercat

Cuestiones relacionadas