В C # 4.0 почему параметр out в методе не может быть ковариантным?
-
22-08-2019 - |
Вопрос
Учитывая этот волшебный интерфейс:
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);
}
Я указываю, что шляпа может быть пустой, используя отдельное возвращаемое логическое значение (предыдущая версия, возможно, вернула бы нулевого кролика из пустой шляпы).Но я по-прежнему вывожу только rabbit, поэтому не делаю ничего логически отличающегося от предыдущей версии.
Компилятор C # 4.0 в CTP выдает ошибку в определении интерфейса - он требует, чтобы параметры метода 'out' имели инвариантный тип.Есть ли жесткая причина, по которой это запрещено, или это что-то, что может быть устранено в будущей версии?
Решение
Интересно.Однако на уровне CLI нет такого понятия, как "out" - только "ref".;существует атрибут, который помогает компиляторам (для определенного назначения), который говорит "вам не нужно передавать это".
Возможно, это ограничение связано с тем, что в CLI нет "out", только "ref".
Другие советы
Хотя это немного хлопотно, вы можете использовать оболочку ковариации:
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));
}
}