2011-02-07 9 views
7
public class ConfigControlBase<T> : UserControl 
    where T : ProviderBase 
{ 
    public T Provider { get; set; } 

    public void Init(T provider) 
    { 
     this.Provider = provider; 
    } 
} 


public abstract class ProviderBase 
{ 
    public abstract ConfigControlBase<ProviderBase> GetControl(); 
} 

public class ProviderXConfigControl : ConfigControlBase<ProviderX> 
{ 
} 

public class ProviderX : ProviderBase 
{ 
    public override ConfigControlBase<ProviderBase> GetControl() 
    { 
     var confControl = new ProviderXConfigControl() as ConfigControlBase<ProviderX>; 
     return confControl; 
    } 
} 

return confControl; se emite una excepción:Al lanzar un tipo de elemento genérico hacia abajo

No se puede convertir implícitamente el tipo ConfigControlBase<ProviderX> a ConfigControlBase<ProviderBase>

Respuesta

21

Vamos a cambiar el nombre de sus clases y propiedades, pero mantener la forma de la misma:

public class Cage<T> where T : Animal 
{ 
    public T Contents { get; set; } 
} 

public class Aquarium : Cage<Fish> { } 

public abstract class Animal 
{ 
    public abstract Cage<Animal> GetCage(); 
} 

public class Fish : Animal 
{ 
    public override Cage<Animal> GetCage() 
    { 
     return (Cage<Animal>)(new Aquarium()); 
    } 
} 

Ahora es Está claro por qué esto no es legal? Supongamos que fuera legal. Entonces podría hacer esto:

Fish fish = new Fish(); 
Cage<Animal> cage = fish.GetCage(); 
cage.contents = new Tiger(); 

Y ahora usted tiene un tigre en su acuario. Y nadie quiere eso.

El compilador (o tiempo de ejecución) tiene que evitar este tipo de error de alguna manera; elige evitarlo lo antes posible. Lo más temprano que puede hacerlo es en la prueba de tipo para la conversión de Aquarium al Cage<Animal>. El compilador sabe que esto eventualmente puede conducir a tigres en acuarios, por lo que no permite la conversión en absoluto. Si fuerza al compilador a permitirlo a través de moldes, falla en tiempo de ejecución.

+8

Increíble metáfora. Ojalá MSDN fuera así. –

1

Esto se debe a ConfigControlBase<ProviderX> no es un ConfigControlBase<ProviderBase>

0

su

public override ConfigControlBase<ProviderBase> GetControl() 

no coincide con

var confControl = new ProviderXConfigControl() as ConfigControlBase<ProviderX>; 
8

tipos genéricos con argumentos de tipo asignables no son asignables a sí mismos.
Por ejemplo, no puede convertir List<string> en List<object>, aunque string es un object.

no es inmediatamente evidente por qué tales fundición no se admite por lo que te voy a dar un ejemplo:

var words = new List<string> { "Serve God", "love me", "mend" }; 
var objects = (List<object>) words; // C# compiler wouldn't allow this 
objects.Add (new Car()); // we just added a Car to Shakespeare's work and the universe exploded 

C# no fomenta explosión universo Sin embargo ya que C# 4.0 se implementa una versión ligera de esta idea . Usted ve, en algunos casos tal colada en realidad sería seguro.

.NET 4.0 trae conceptos de covarianza y contravarianza en genéricos solo para interfaces y delega, es posible que desee comprobar esto.

Ejemplo (no funciona antes de .NET 4.0):

void HandleCollection (IEnumerable<object> collection) 
{ 
    // ... 
} 

var words = new List<string> { "Serve God", "love me", "mend" }; 

// IEnumerable is defined as IEnumerable<out T> in .NET 4.0 
// 'out' keyword guarantees that T is only used for return values 
// and therefore client code can't explode the universe 

var objects = (IEnumerable<object>) words; 
HandleCollection (objects); 
+4

"C# no fomenta la explosión del universo" es mi nuevo lema. –

+1

Tenga en cuenta que * la covarianza no funciona en los tipos de valor *. No puede convertir una lista de int en un IEnumerable porque la memoria tiene que asignarse para el boxeo, y no hay ningún código emitido para hacer eso. Puede convertir una lista de cadenas en IEnumerable , porque string es un tipo de referencia. –

+0

Cambié ejemplos a 'cadena' para mantener la coherencia. Gracias por el comentario. –

0

Esta respuesta puede no ser útil en su escenario, ya que probablemente debería buscar otra solución, pero durante la reflexión encontré la capacidad de convertir a tipos menos genéricos, por lo que escribí una solución para ello. Sin embargo, solo funciona para interfaces, y usted debe garantizar que solo pasará objetos de los tipos correctos a la interfaz.

Básicamente, generan una clase de proxy en tiempo de ejecución que hace todos los moldes necesarios para usted. Es el uso de mira de la siguiente manera:

object validator; // An object known to implement IValidation<T>. 
object toValidate; // The object which can be validated by using the validator. 

// Assume validator is IValidation<string> and toValidate a string. 

IValidation<object> validation 
    = Proxy.CreateGenericInterfaceWrapper<IValidation<object>>(validator); 

validation.IsValid(toValidate); // This works! No need to know about the type. 

// The following will throw an InvalidCastException. 
//validation.IsValid(10); 

Más información y el código fuente se puede encontrar on my blog.

Cuestiones relacionadas