In C# 4.0 why can't an out parameter in a method be covariant?

邮差的信 提交于 2019-12-22 01:43:24

问题


Given this magical interface:

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

And this class hierarchy:

public class Rabbit { }

public class WhiteRabbit : Rabbit { }

I can now compile this:

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

Which is great. But what if I define the interface differently:

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

I'm indicating that the hat might be empty, using a separate boolean return value (the previous version would perhaps have returned a null rabbit from an empty hat). But I'm still only outputting a rabbit, so not doing anything logically different to the previous version.

The C# 4.0 compiler in the CTP gives an error in the interface definition - it requires 'out' method parameters to be of an invariant type. Is there a hard-and-fast reason why this isn't allowed, or is it something that might be addressed in a future version?


回答1:


Interesting. However, at the CLI level there is no such thing as "out" - only "ref"; there is an attribute that helps compilers (for definite assignment) that says "you don't need to pass it in".

Maybe this restriction is because the CLI doesn't have "out", only "ref".




回答2:


Although it's a bit of a hassle, you can use a covariance wrapper:

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

This lets you keep the collection as it was originally typed and refer to it covariantly without creating a detached copy, so that updates to the original are seen in the covariant use. Example:

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


来源:https://stackoverflow.com/questions/527758/in-c-sharp-4-0-why-cant-an-out-parameter-in-a-method-be-covariant

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!