параметры ref и out в C # и не могут быть помечены как вариант - PullRequest
13 голосов
/ 20 мая 2010

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

Отсюда

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

Ответы [ 4 ]

45 голосов
/ 21 мая 2010

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

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

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

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

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

T появляется в позиции ввода? Да. Вызывающий может передать значение T через элемент; Колли Фу может прочитать это. Поэтому 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 также не может быть помечен "в".

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

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

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

К сожалению нет. "out" на самом деле не отличается от "ref" за кадром. Единственное различие между «out» и «ref» состоит в том, что компилятор запрещает чтение из параметра out, прежде чем он будет назначен вызываемым объектом, и что компилятору требуется присвоение, прежде чем вызываемый объект возвратится нормально. Кто-то, кто написал реализацию этого интерфейса на языке .NET, отличном от C # , сможет прочитать элемент до его инициализации, и, следовательно, его можно будет использовать в качестве входных данных. Поэтому мы запрещаем помечать T как «out» в этом случае. Это прискорбно, но мы ничего не можем с этим поделать; мы должны соблюдать правила безопасности типов 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(); 
    }
}

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

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


ОБНОВЛЕНИЕ: C # 7 добавил in в качестве формального объявления параметра, что означает, что теперь у нас есть и in и out, означающие две вещи; это создаст некоторую путаницу. Позвольте мне прояснить это:

  • in, out и ref при объявлении формального параметра в списке параметров означает, что «этот параметр является псевдонимом переменной, предоставленной вызывающей стороной».
  • ref означает «вызываемый может прочитать или записать переменную с псевдонимом, и она должна быть известна для назначения перед вызовом.
  • out означает «вызываемый должен записать переменную с псевдонимом через псевдоним, прежде чем он вернется нормально». Это также означает, что вызываемый объект не должен читать переменную с псевдонимом через псевдоним перед тем, как записать ее, поскольку переменная не может быть точно назначена.
  • in означает «вызываемый может прочитать псевдоним, но не записывает в него псевдоним». Цель 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 и, следовательно, доступны для чтения и записи.

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

1 голос
/ 30 сентября 2018

Можно использовать ковариацию без параметров, но вам нужны две структуры. Например, вы можете поместить параметр 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; }
}
1 голос
/ 20 мая 2010

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

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

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

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);
0 голосов
/ 22 декабря 2011

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

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!
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...