代码之家  ›  专栏  ›  技术社区  ›  Daniel Earwicker

在C#4.0中,为什么方法中的out参数不能是协变的?

  •  16
  • Daniel Earwicker  · 技术社区  · 17 年前

    鉴于这种神奇的界面:

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

    我使用一个单独的布尔返回值指示hat可能是空的(以前的版本可能会从空hat返回一个null rabbit)。但是我仍然只输出一个兔子,所以没有做任何与前一个版本在逻辑上不同的事情。

    CTP中的C#4.0编译器在接口定义中给出了一个错误——它要求“out”方法参数为不变类型。有没有一个硬性的理由不允许这样做,或者这可能会在未来的版本中得到解决?

    2 回复  |  直到 17 年前
        1
  •  9
  •   Marc Gravell    17 年前

    有趣的但是,在CLI级别没有“out”之类的东西,只有“ref”;有一个属性可以帮助编译器(用于确定赋值),它表示“您不需要传入”。

    这个限制可能是因为CLI没有“out”,只有“ref”。

        2
  •  0
  •   Glenn Slayden    15 年前

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