В C # 4.0 почему параметр out в методе не может быть ковариантным? - PullRequest
16 голосов
/ 09 февраля 2009

Учитывая этот волшебный интерфейс:

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

И эта иерархия классов:

public class Rabbit { }

public class WhiteRabbit : Rabbit { }

Теперь я могу скомпилировать это:

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

Что здорово. Но что, если я определю интерфейс по-другому:

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

Я указываю, что шляпа может быть пустой, используя отдельное логическое возвращаемое значение (предыдущая версия, возможно, вернула бы нулевого кролика из пустой шляпы). Но я по-прежнему только выводю кролика, поэтому не делаю ничего логически отличного от предыдущей версии.

Компилятор C # 4.0 в CTP выдает ошибку в определении интерфейса - он требует, чтобы параметры метода out были инвариантного типа. Есть ли серьезная причина, почему это не разрешено, или это что-то, что может быть исправлено в будущей версии?

Ответы [ 2 ]

9 голосов
/ 09 февраля 2009

Интересно. Однако на уровне CLI не существует такой вещи, как «out» - только «ref»; есть атрибут, который помогает компиляторам (для определенного назначения), который говорит: «Вам не нужно передавать его».

Может быть, это ограничение, потому что CLI не имеет "out", только "ref".

0 голосов
/ 19 февраля 2011

Хотя это немного хлопотно, вы можете использовать ковариационную оболочку:

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

Это позволяет сохранить коллекцию в том виде, в котором она была первоначально напечатана, и ссылаться на нее ковариантно, не создавая отдельную копию, чтобы обновления оригинала рассматривались в ковариантном использовании. Пример:

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