2009-02-09 7 views
14

Biorąc pod uwagę ten magiczny interfejs:W języku C# 4.0, dlaczego parametr out w metodzie nie może być kowariancyjny?

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

a ta klasa hierarchii:

public class Rabbit { } 

public class WhiteRabbit : Rabbit { } 

Mogę teraz skompilować to:

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

co jest dobre. Ale co, jeśli mogę zdefiniować interfejs inaczej:

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

mam wskazujący, że kapelusz może być pusta, za pomocą oddzielnego wartości zwracanej logiczną (poprzednia wersja będzie prawdopodobnie wrócili null królika z pustego kapelusza). Ale wciąż wysyłam tylko królika, więc nie robię nic logicznie innego niż poprzednia wersja.

Kompilator C# 4.0 w CTP podaje błąd w definicji interfejsu - wymaga, aby parametry metody "out" były typu niezmiennego. Czy istnieje niełatwy powód, dla którego nie jest to dozwolone, czy jest to coś, co można by rozwiązać w przyszłej wersji?

Odpowiedz

9

Interesujące. Jednak na poziomie CLI nie ma czegoś takiego jak "out" - tylko "ref"; istnieje atrybut, który pomaga kompilatorom (dla określonego zadania), który mówi "nie musisz go przekazywać".

Może to ograniczenie jest spowodowane tym, że CLI nie ma "out", tylko "ref".

+0

Dla informacji znalazłem wiele blogów itp. Mówiących to samo, ale żaden z komentarzy nie pochodzi z "oficjalnych" źródeł MS. Jestem całkiem pewny, że jest poprawny, chociaż ... wariancja C# 4.0 nadal opiera się na regułach CLI. –

+0

To brzmi dość prawdopodobne! –

0

Chociaż jest to trochę kłopotów, można użyć otoki kowariancji:

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

To pozwala utrzymać kolekcję jak to było pierwotnie wpisane i odnoszą się do niego covariantly bez tworzenia wolnostojący kopię, dzięki czemu aktualizacje oryginału są widoczne w użyciu kowariantnym. Przykład:

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)); 
    } 
} 
Powiązane problemy