2009-02-09 8 views
14

Étant donné cette interface magique:En C# 4.0, pourquoi un paramètre out dans une méthode ne peut-il pas être covariant?

public interface IHat<out TRabbit> 
{ 
    TRabbit Take(); 
} 

Et cette hiérarchie de classe:

public class Rabbit { } 

public class WhiteRabbit : Rabbit { } 

Je peux maintenant compiler ceci:

IHat<WhiteRabbit> hat1 = null; 
IHat<Rabbit> hat2 = hat1; 

Ce qui est génial. Mais si je définir l'interface différemment:

public interface IHat<out TRabbit> 
{ 
    bool Take(out TRabbit r); 
} 

J'indiquais que le chapeau peut être vide, en utilisant une valeur de retour booléen (la version précédente aurait peut-être retourné un lapin nul d'un chapeau vide). Mais je ne fais que sortir un lapin, donc je ne fais rien de différent par rapport à la version précédente.

Le compilateur C# 4.0 dans le CTP donne une erreur dans la définition de l'interface - il exige que les paramètres de la méthode 'out' soient de type invariant. Y a-t-il une raison dure et rapide pour laquelle cela n'est pas autorisé, ou est-ce quelque chose qui pourrait être traité dans une future version?

Répondre

9

Intéressant. Cependant, au niveau de la CLI, il n'y a pas de «sortie» - seulement «ref»; il y a un attribut qui aide les compilateurs (pour une assignation définie) qui dit "vous n'avez pas besoin de le transmettre".

Peut-être que cette restriction est parce que le CLI n'a pas "out", seulement "ref".

+0

Pour info, j'ai trouvé plusieurs blogs etc disant la même chose, mais aucun des commentaires proviennent de sources MS "officielles". Je suis assez confiant que c'est correct, cependant ... la variance C# 4.0 est toujours basée sur les règles CLI. –

+0

Cela semble assez probable! –

0

Bien qu'il soit un peu embêtant, vous pouvez utiliser une enveloppe de covariance:

public class CovariantListWrapper<TOut, TIn> : IList<TOut> where TIn : TOut 
{ 
    IList<TIn> list; 

    public CovariantListWrapper(IList<TIn> list) 
    { 
     this.list = list; 
    } 

    public int IndexOf(TOut item) 
    { 
     // (not covariant but permitted) 
     return item is TIn ? list.IndexOf((TIn)item) : -1; 
    } 

    public TOut this[int index] 
    { 
     get { return list[index]; } 
     set { throw new InvalidOperationException(); } 
    } 

    public bool Contains(TOut item) 
    { 
     // (not covariant but permitted) 
     return item is TIn && list.Contains((TIn)item); 
    } 

    public void CopyTo(TOut[] array, int arrayIndex) 
    { 
     foreach (TOut t in this) 
      array[arrayIndex++] = t; 
    } 

    public int Count { get { return list.Count; } } 

    public bool IsReadOnly { get { return true; } } 

    public IEnumerator<TOut> GetEnumerator() 
    { 
     foreach (TIn t in list) 
      yield return t; 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 

    public void Insert(int index, TOut item) { throw new InvalidOperationException(); } 
    public void RemoveAt(int index) { throw new InvalidOperationException(); } 
    public void Add(TOut item) { throw new InvalidOperationException(); } 
    public void Clear() { throw new InvalidOperationException(); } 
    public bool Remove(TOut item) { throw new InvalidOperationException(); } 
} 

Cela vous permet de garder la collection comme il a été saisi et se référer à elle covariante sans créer une copie détachée, de sorte que mises à jour de l'original sont vus dans l'utilisation covariant. Exemple:

class CovarianceWrapperExample 
{ 
    class Person { } 
    class Employee : Person { } 

    void ProcessPeople(IList<Person> people) { /* ... */ } 

    void Foo() 
    { 
     List<Employee> employees = new List<Employee>(); 

     // cannot do: 
     ProcessPeople(employees); 

     // can do: 
     ProcessPeople(new CovariantListWrapper<Person, Employee>(employees)); 
    } 
} 
Questions connexes