2009-02-09 12 views
14

Dato questo magico interfaccia:In C# 4.0 perché un parametro out in un metodo non può essere covariante?

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

E questa gerarchia di classe:

public class Rabbit { } 

public class WhiteRabbit : Rabbit { } 

ora posso compilare questo:

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

che è grande. Ma cosa succede se io definisco l'interfaccia in modo diverso:

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

sto indicando che il cappello potrebbe essere vuoto, con un valore di ritorno booleano separato (la versione precedente sarebbe forse tornato un coniglio da un cappello nulla vuoto). Ma sto solo emettendo un coniglio, quindi non sto facendo nulla di logicamente diverso dalla versione precedente.

Il compilatore C# 4.0 nel CTP fornisce un errore nella definizione dell'interfaccia - richiede che i parametri del metodo 'out' siano di tipo invariante. C'è una ragione per cui questo non è permesso o è qualcosa che potrebbe essere risolto in una versione futura?

risposta

9

Interessante. Tuttavia, a livello di CLI non esiste "out" - solo "ref"; c'è un attributo che aiuta i compilatori (per un compito definito) che dice "non è necessario passarlo".

Forse questa restrizione è dovuta al fatto che la CLI non ha "out", solo "ref".

+0

Per informazioni, ho trovato più blog ecc dicendo lo stesso, ma nessuno dei commenti proviene da fonti MS "ufficiali". Sono abbastanza sicuro che sia corretto, anche se ... la varianza C# 4.0 è ancora basata sulle regole della CLI. –

+0

Sembra abbastanza probabile! –

0

Anche se è un po 'di fastidio, è possibile utilizzare un wrapper covarianza:

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(); } 
} 

Ciò consente di mantenere la collezione come era stato originariamente digitato e si riferiscono ad esso covariantly senza creare una copia indipendente, in modo che gli aggiornamenti all'originale sono visti nell'uso covariante. Esempio:

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)); 
    } 
} 
Problemi correlati