2012-01-18 12 views
10

No estoy seguro de por qué el siguiente fragmento de código no es covariante.¿Por qué los parámetros de tipo genérico C# infringen la covarianza?

public interface IResourceColl<out T> : IEnumerable<T> where T : IResource { 

    int Count { get; } 

    T this[int index] { get; } 

    bool TryGetValue(string SUID, out T obj); // Error here? 
    } 

de error 1 varianza no válido: El parámetro de tipo 'T' debe ser invariablemente válida en 'IResourceColl.TryGetValue (cadena, fuera T)'. 'T' es covariante.

Mi interfaz solo utiliza el parámetro de plantilla en las posiciones de salida. Yo podría refactorizar el código a algo así como

public interface IResourceColl<out T> : IEnumerable<T> where T : class, IResource { 

    int Count { get; } 

    T this[int index] { get; } 

    T TryGetValue(string SUID); // return null if not found 
    } 

pero estoy tratando de entender si mi código original en realidad viola covarianza o si se trata de un compilador o .NET limitación de covarianza.

+0

posible duplicado de [C#: ¿Por qué no lo hace 'ref' y 'hacia fuera' polimorfismo de apoyo] (http: // stackoverflow. com/questions/1207144/c-sharp-why-doesnt-ref-and-out-support-polymorphism) – Jon

+0

Lo importante a tener en cuenta aquí es que ['out'] (http://msdn.microsoft.com/en -us/library/ee332485.aspx) (el modificador de parámetro) no está relacionado con ['out'] (http://msdn.microsoft.com/en-us/library/dd469487.aspx) (utilizado en un tipo genérico parámetro). – Jon

+0

@Jon - esa pregunta se aplica a C# 3.0 y antes. La sintaxis descrita aquí es C# 4.0 – Oded

Respuesta

11

El problema es, en efecto aquí:

bool TryGetValue(string SUID, out T obj); // Error here? 

Has marcado obj como out parámetro, que todavía significa sin embargo que está de paso enobj por lo que no puede ser covariante, ya que ambos pasan en una instancia de escriba T y devuélvala.

Editar:

Eric Lippert dice mejor que nadie me refiero a his answer to "ref and out parameters in C# and cannot be marked as variant" y lo cito en lo que respecta a out parámetros:

caso de que sea legal para tomar T marcado como "fuera" ? Lamentablemente no. "fuera" en realidad no es diferente de "ref" detrás de escena. La única diferencia entre entre "salir" y "ref" es que el compilador prohíbe leer un parámetro out antes de que sea asignado por el destinatario, y que el compilador requiere asignación antes de que el destinatario vuelva normalmente al . Alguien que escribió una implementación de esta interfaz en un idioma .NET distinto de C# podría leer el elemento antes de se inicializó y, por lo tanto, podría usarse como una entrada. Nos por lo tanto, prohíbe marcar T como "fuera" en este caso. Eso es lamentable, pero no podemos hacer nada al respecto; debemos obedecer las reglas de seguridad tipo del CLR.

+0

Podría pasar una instancia de algo que no sea una T, pero las reglas de C# requieren que asigne algo antes de que pueda leerlo, por lo que nunca causará un problema. – MerickOWA

+0

Ah, ahora veo que es posible leer el valor en otros idiomas, y el blog de eric señala casos incluso en C# donde "podría" romperse. Eso explica por qué se viola la covarianza. – MerickOWA

+0

El problema es que los parámetros llamados 'out' realmente no lo son; un parámetro real 'out' haría que el compilador generara un tipo de estructura para el retorno de la función, que incluiría el tipo de retorno designado y todos los parámetros' out'; la persona que llama copiaría automáticamente los campos apropiados de los parámetros struct a 'out', y luego consideraría el campo restante (si lo hubiera) como el valor de retorno. Si los parámetros 'out' se implementaran de esa manera, podrían ser covariantes. – supercat

1

Viola covarianza debido a que el valor proporcionado a los parámetros de salida debe ser de exactamente del mismo tipo que la declaración de parámetros de salida. Por ejemplo, suponiendo T era una cadena, covarianza implicaría que no estaría mal hacer

var someIResourceColl = new someIResourceCollClass<String>(); 
Object k; 
someIResourceColl.TryGetValue("Foo", out k); // This will break because k is an Object, not a String 
1

Examine este pequeño ejemplo, y se entiende por qué no se permite:

public void Test() 
{ 
    string s = "Hello"; 
    Foo(out s); 
} 

public void Foo(out string s) //s is passed with "Hello" even if not usable 
{ 
    s = "Bye"; 
} 

out significa que s debe asignarse definitivamente antes de que la ejecución abandone el método y, a la inversa, no puede usar s hasta que esté definitivamente asignado en el cuerpo del método.Esto parece ser compatible con reglas de covarianza. Pero nada le impide asignar s en el sitio de llamadas antes de llamar al método. Este valor se pasa al método, lo que significa que incluso si no es utilizable está pasando efectivamente un parámetro de un tipo definido al método que va en contra de las reglas de covarianza que establecen que el tipo genérico solo puede ser utilizado como el tipo de devolución de un método.

3

Aquí está la posible solución usando el método de extensión. No necesariamente conveniente desde el punto de vista implementador, pero el usuario debe ser feliz:

public interface IExample<out T> 
{ 
    T TryGetByName(string name, out bool success); 
} 

public static class HelperClass 
{ 
    public static bool TryGetByName<T>(this IExample<T> @this, string name, out T child) 
    { 
     bool success; 
     child = @this.TryGetByName(name, out success); 
     return success; 
    } 
} 

public interface IAnimal { }; 

public interface IFish : IAnimal { }; 

public class XavierTheFish : IFish { }; 

public class Aquarium : IExample<IFish> 
{ 
    public IFish TryGetByName(string name, out bool success) 
    { 
     if (name == "Xavier") 
     { 
      success = true; 
      return new XavierTheFish(); 
     } 
     else 
     { 
      success = false; 
      return null; 
     } 
    } 
} 

public static class Test 
{ 
    public static void Main() 
    { 
     var aquarium = new Aquarium(); 
     IAnimal child; 
     if (aquarium.TryGetByName("Xavier", out child)) 
     { 
      Console.WriteLine(child); 
     } 
    } 
} 
Cuestiones relacionadas