在C#中引用和输出参数,不能标记为变体

该陈述是什么意思?

从这里

在C#中引用和输出参数,不能标记为变体。

1)是否意味着不能做以下事情。

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

2)或者它是否意味着我不能拥有以下内容。

 public delegate R Reader(A arg, string s); public static void AssignReadFromPeonMethodToDelegate(ref Reader pReader) { pReader = ReadFromPeon; } static object ReadFromPeon(Peon p, string propertyName) { return p.GetType().GetField(propertyName).GetValue(p); } static Reader pReader; static void Main(string[] args) { AssignReadFromPeonMethodToDelegate(ref pReader); bCanReadWrite = (bool)pReader(peon, "CanReadWrite"); Console.WriteLine("Press any key to quit..."); Console.ReadKey(); } 

我试过(2)并且它有效。

粗略地说,“出”意味着“仅出现在输出位置”。

粗略地说,“在”中意味着“仅出现在输入位置”。

真实的故事比这更复杂,但选择关键词是因为大部分时间都是如此。

考虑一个接口的方法或委托代表的方法:

 delegate void Foo(ref T item); 

T出现在输入位置吗? 是。 调用者可以在via项中传递T值; 被叫者Foo可以读取它。 因此T不能标记为“out”。

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

因此,如果T出现在“ref”forms参数中,则T不能标记为in或out。

让我们看看出现问题的一些真实例子。 假设这是合法的:

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

好吧我的猫,我们只是做了一个猫皮。 “出局”不合法。

那个“在”怎么样?

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

我们只是把一只猫放在一个只能容纳狗的变量中。 T也不能标记为“in”。

out参数怎么样?

 delegate void Foo(out T item); 

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

很不幸的是,不行。 “out”实际上与幕后的“ref”没有什么不同。 “out”和“ref”之间的唯一区别是编译器禁止在被调用者分配之前从out参数读取,并且编译器在被调用者正常返回之前需要赋值。 以C#以外的.NET语言编写此接口的实现的人将能够在初始化之前从该项读取,因此可以将其用作输入。 因此,在这种情况下,我们禁止将T标记为“out”。 这是令人遗憾的,但我们无能为力; 我们必须遵守CLR的类型安全规则。

此外,“out”参数的规则是它们在写入之前不能用于输入。 没有规则在写入不能用于输入。 假设我们允许

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

我们再一次做了一只猫吠。 我们不能让T“出局”。

以这种方式输出参数用于输入是非常愚蠢的,但是合法的。

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

 public delegate R MyDelegate(ref A arg); 

编辑: @Eric Lippert纠正我,这个仍然是合法的:

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

它实际上是有道理的,因为Rgenerics参数未标记为变体,因此它不违反规则。 但是,这个仍然是非法的:

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

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

 interface IFoo { T Get(); //bool TryGet(out T value); // doesn't work: Invalid variance: The type parameter 'T' must be invariantly valid on 'IFoo.TryGet(out T)'. 'T' is covariant. bool TryGet(Action value); // works! } 

可以使用带有out参数的协方差,但是您需要两个结构。 例如,您可以将out参数放在扩展方法上:

 public static class ResultExtension{ public static bool TryGetValue(this IResult self, out T res) { if (self.HasValue) { res = self.Value; return true; } res = default; return false; } } public interface IResult { bool HasValue { get; } T Value { get; } }