параметры Ref и Out в C # и не могут быть помечены как вариант

StackOverflow https://stackoverflow.com/questions/2876315

Вопрос

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

Отсюда

Параметры REF и OUT в C # и не могут быть помечены как вариант.

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), и это сработало.

Это было полезно?

Решение

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

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

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

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

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

Не отображается в входном положении? да. Вызывающий абонент может пройти значение t в по п.; Callee Foo может прочитать это. Поэтому T не может быть отмечен «из».

Не отображается в выходном положении? да. Callee может написать новое значение для элемента, который затем вызывает абонент. Поэтому T не может быть отмечен «в».

Следовательно, если T появляется в формальном параметре «REF», T нельзя пометить как в или OUT.

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

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

Ну, собака мои кошки, мы только что сделали кошачьей коры. «OUT» не может быть законным.

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

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

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

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

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

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

К сожалению нет. «OUT» на самом деле не отличается от «ref» за кулисами. Единственная разница между «OUT» и «REF» заключается в том, что компилятор запрещает чтение от параметра OUT, прежде чем он назначен Callee, и что компилятор требует назначения до возврата Callee. Кто-то, кто написал реализацию этого интерфейса в .NET язык, отличный от C # сможет прочитать из элемента, прежде чем он был инициализирован, и поэтому его можно использовать в качестве ввода. Поэтому мы запрещаем маркировку T как «вне» в этом случае. Это прискорбно, но ничего, что мы не можем с этим сделать; Мы должны подчиняться правилам безопасности типа CLR.

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

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

Еще раз мы сделали кошачьей коры. Мы не можем позволить «быть».

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


Обновление: C # 7 добавил in как формальное объявление параметров, что означает, что у нас сейчас есть оба in а также out то есть две вещи; Это собирается создать некоторую путаницу. Позвольте мне очистить это:

  • in, out а также ref на Официальное объявление параметров в списке параметров Средства «Этот параметр - псевдоним для переменной, предоставленной звонящем».
  • ref Средства «Callee может прочитать или записывать псевдонимаемую переменную, и она должна быть известна, чтобы быть назначена до вызова.
  • out Средства «Callee) должен написать псевную переменную через псевдоним, прежде чем она возвращается нормально». Это также означает, что Callee не должен читать псевную переменную через псевдоним, прежде чем она пишет ее, потому что переменная может быть определенно назначена.
  • in Средства «Callee может прочитать псевную переменную, но не пишет ему через псевдоним». Цель in это решить редкую проблему исполнения, в результате чего большая структура должна быть передана «значением», но это дорого, чтобы сделать это. Как деталь внедрения, in Параметры обычно передаются через значение по размеру указателя, что быстрее, чем копирование по значению, но медленнее на развесел.
  • С точки зрения CLR, in, out а также ref все одно и то же; Правила о том, кто читает и пишет какие переменные в какое время, CLR не знает или заботится.
  • Поскольку это CLR, который обеспечивает правила о дисперсии, правила, которые применяются к ref также относится к in а также out Параметры.

Напротив, in а также out Объявления параметров типа означают «Этот тип параметра не должен использоваться в ковариантной манере», и «этот параметр типа не должен использоваться контрафарентом» соответственно.

Как отмечалось выше, мы выбрали in а также out для тех модификаторов, потому что если мы видим IFoo<in T, out U> тогда T используется в положениях «ввода» и U используется в положениях «Выходных». Хотя это не строго Правда, это верно достаточно в случае использования 99,9%, что это полезное мнемоническое.

К сожалению, interface IFoo<in T, out U> { void Foo(in T t, out U u); } является незаконным, потому что похоже, что он должен работать. Это не может работать из-за перспективы CLR верификатора, это оба ref Параметры и, следовательно, чтение-запись.

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

Другие советы

Это означает, что вы не можете иметь следующую декларацию:

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

Редактировать: @Eric Lippert скорректировал меня, что это все еще законно:

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

Это на самом деле имеет смысл, поскольку R итоговый параметр не помечен как вариант, поэтому он не нарушает правило. Тем не менее, это все еще незаконно:

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

Можно использовать ковариацию без параметров, но вам нужны две структуры. Например, вы можете поставить параметр OUT на метод расширения:

public static class ResultExtension{
    public static bool TryGetValue<T>(this IResult<T> self, out T res) {
        if (self.HasValue) {
            res = self.Value;
            return true;

        }
        res = default;
        return false;
    }
}

public interface IResult<out T>
{
    bool HasValue { get; }
    T Value { get; }
}

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

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!
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top