Ref и out в С# и не может быть помечен как вариант

Что означает выражение?

Отсюда

параметры ref и out в С# и не может быть помечен как вариант.

1) Означает ли это, что следующее невозможно сделать.

public class SomeClass<R, A>: IVariant<R, A>
{
    public virtual R DoSomething( ref A args )
    {
        return null;
    }
}

2) Или это означает, что у меня не может быть следующего.

public delegate R Reader<out R, in A>(A arg, string s);

public static void AssignReadFromPeonMethodToDelegate(ref Reader<object, Peon> pReader)
{
    pReader = ReadFromPeon;
}

static object ReadFromPeon(Peon p, string propertyName)
    {
        return p.GetType().GetField(propertyName).GetValue(p);
    }

static Reader<object, Peon> pReader;

static void Main(string[] args)
    {
        AssignReadFromPeonMethodToDelegate(ref pReader);
        bCanReadWrite = (bool)pReader(peon, "CanReadWrite");

        Console.WriteLine("Press any key to quit...");
        Console.ReadKey();
    }

Я пробовал (2) и работал.

Ответ 1

"out" означает, грубо говоря, "появляется только в выходных позициях".

"in" означает, грубо говоря, "появляется только во входных позициях".

Реальная история немного сложнее, но ключевые слова были выбраны, потому что большую часть времени это происходит.

Рассмотрим метод интерфейса или метод, представленный делегатом:

delegate void Foo</*???*/ T>(ref T item);

Появляется ли T во входной позиции? Да. Вызывающий может передать значение T через элемент; вызывающий Foo может это прочитать. Поэтому T не может быть отмечен "out" .

Появляется ли T в выходной позиции? Да. Вызывающий может написать новое значение для элемента, которое вызывающий может затем прочитать. Поэтому T не может быть помечен как "in" .

Поэтому, если T появляется в формальном параметре "ref" , T не может быть помечен как входом, так и вне.

Посмотрите на некоторые реальные примеры того, как все пошло не так. Предположим, что это было законно:

delegate void X<out T>(ref T item);
...
X<Dog> x1 = (ref Dog d)=>{ d.Bark(); }
X<Animal> x2 = x1; // covariant;
Animal a = new Cat();
x2(ref a);

Хорошо, собака, мои кошки, мы просто сделали кошку. "выход" не может быть законным.

Как насчет "в"?

delegate void X<in T>(ref T item);
...
X<Animal> x1 = (ref Animal a)=>{ a = new Cat(); }
X<Dog> x2 = x1; // contravariant;
Dog d = new Dog();
x2(ref d);

И мы просто ставим кошку в переменную, которая может содержать только собак. T также не может быть помечен как "in" .

Как насчет параметра out?

delegate void Foo</*???*/T>(out T item);

? Теперь T появляется только в выходной позиции. Должно ли быть законным делать T отмеченным как "вне" ?

К сожалению нет. "выход" на самом деле не отличается от "ref" за кулисами. Единственное различие между "out" и "ref" заключается в том, что компилятор запрещает чтение из параметра out до того, как он назначен вызываемым, и что компилятор требует назначения до того, как вызываемый возвращает нормально. Кто-то, кто написал реализацию этого интерфейса на языке .NET, отличном от С#, мог бы читать из элемента до его инициализации, и поэтому его можно было бы использовать в качестве ввода. Поэтому мы запрещаем обозначать Т как "вне" в этом случае. Это прискорбно, но мы ничего не можем с этим поделать; мы должны соблюдать правила безопасности типа CLR.

Кроме того, правило "выходных" параметров заключается в том, что они не могут использоваться для ввода до их записи. Нет правила, что они не могут использоваться для ввода после того, как они записаны. Предположим, что мы допустили

delegate void X<out T>(out T item);
class C
{
    Animal a;
    void M()
    {
        X<Dog> x1 = (out Dog d) => 
        { 
             d = null; 
             N(); 
             if (d != null) 
               d.Bark(); 
        };
        x<Animal> x2 = x1; // Suppose this were legal covariance.
        x2(out this.a);
    }
    void N() 
    { 
        if (this.a == null) 
            this.a = new Cat(); 
    }
}

Еще раз мы сделали кошку коры. Мы не можем позволить T быть "вне" .

Очень глупо использовать параметры для ввода таким образом, но легальные.

Ответ 2

Это означает, что вы не можете иметь следующее объявление:

public delegate R MyDelegate<out R, in A>(ref A arg);

Изменить: @Эрик Липперт исправил меня, что этот вопрос все еще легален:

public delegate void MyDelegate<R, in A>(A arg, out R s);

Это действительно имеет смысл, так как параметр R generic не отмечен как вариант, поэтому он не нарушает правило. Однако этот факт по-прежнему является незаконным:

public delegate void MyDelegate<out R, in A>(A arg, out R s);

Ответ 3

Но следующий код может быть скомпилирован:

interface IFoo<out T>
{
    T Get();

    //bool TryGet(out T value); // doesn't work: Invalid variance: The type parameter 'T' must be invariantly valid on 'IFoo<T>.TryGet(out T)'. 'T' is covariant.

    bool TryGet(Action<T> value); // works!
}