14

What does the statement mean?

From here

ref and out parameters in C# and cannot be marked as variant.

1) Does it mean that the following can not be done.

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

2) Or does it mean I cannot have the following.

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

I tried (2) and it worked.

4

4 回答 4

53

“out”的意思,粗略地说,“只出现在输出位置”。

“in”的意思,粗略地说,“只出现在输入位置”。

真实的故事比这要复杂一些,但是选择关键字是因为大多数时候都是这种情况。

考虑接口的方法或委托表示的方法:

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

T 是否出现在输入位置?是的。调用者可以通过 item 传入一个 T 的值;被调用者 Foo 可以阅读。因此 T 不能被标记为“out”。

T 是否出现在输出位置?是的。被调用者可以将新值写入项目,然后调用者可以读取该值。因此,T 不能被标记为“in”。

因此,如果 T 出现在“ref”形式参数中,则不能将 T 标记为 in 或 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);

好吧,我的猫,我们刚刚做了一次猫叫。“出”是不合法的。

“在”呢?

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”。

输出参数呢?

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

? 现在 T 只出现在输出位置。将 T 标记为“out”是否合法?

抱歉不行。“out”实际上与幕后的“ref”没有什么不同。“out”和“ref”的唯一区别是编译器禁止在被调用者分配之前从out参数读取,并且编译器要求在被调用者正常返回之前进行赋值。使用 C# 以外的 .NET 语言编写此接口的实现的人将能够在项目初始化之前读取它,因此它可以用作输入。因此,在这种情况下,我们禁止将 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 “出局”。

以这种方式使用out参数进行输入是非常愚蠢的,但是合法的。


更新:C# 7 添加了in作为形式参数声明,这意味着我们现在拥有这两者inout意味着两件事;这会造成一些混乱。让我澄清一下:

  • inout并且ref参数列表中的形式参数声明上表示“此参数是调用者提供的变量的别名”。
  • ref意思是“被调用者可以读取或写入别名变量,并且在调用之前必须知道它已被赋值。
  • out表示“被调用者必须通过别名写入别名变量才能正常返回”。这也意味着被调用者在写入别名之前不得通过别名读取别名变量,因为该变量可能未明确分配。
  • in表示“被调用者可以读取别名变量,但不会通过别名写入它”。的目的in是解决一个罕见的性能问题,即必须“按值”传递大型结构,但这样做代价高昂。作为实现细节,in参数通常通过指针大小的值传递,这比按值复制要快,但在取消引用时要慢。
  • 从 CLR 的角度来看,inout都是ref一回事;关于谁在什么时间读写什么变量的规则,CLR 不知道也不关心。
  • 由于是 CLR 执行有关方差的规则,因此适用的规则ref也适用于inout参数。

相反,inout类型参数声明上,分别表示“此类型参数不得以协变方式使用”和“此类型参数不得以逆变方式使用”。

如上所述,我们为这些修饰符选择inandout是因为如果我们看到IFoo<in T, out U>thenT用于“输入”位置并U用于“输出”位置。虽然这并不完全正确,但在 99.9% 的用例中它是一个有用的助记符,这已经足够正确了。

不幸的是,这interface IFoo<in T, out U> { void Foo(in T t, out U u); }是非法的,因为它看起来应该起作用。它无法工作,因为从 CLR 验证者的角度来看,它们都是ref参数,因此是可读写的。

这只是那些在逻辑上应该一起工作的两个功能由于实现细节原因而不能很好地一起工作的奇怪的、意想不到的情况之一。

于 2010-05-20T20:24:05.457 回答
1

这意味着您不能拥有以下声明:

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);
于 2010-05-20T17:57:06.240 回答
1

Eric Lippert 很好地解释了为什么存在这种约束。

如果您正在寻找解决此限制的方法。你可以这样做:

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

这是有效的,因为有两种结构。一种结构获得协方差,另一种获得输出参数。out 参数未标记为变体,因此编译器很高兴。

于 2018-09-30T01:00:16.127 回答
0

但是,可以编译以下代码:

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!
}
于 2011-12-22T13:50:46.297 回答