在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*???*/ T>(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*???*/T>(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; } }