为什么不将违反方案委托给价值类型?

此代码段未在LINQPad中编译。

void Main() { (new[]{0,1,2,3}).Where(IsNull).Dump(); } static bool IsNull(object arg) { return arg == null; } 

编译器的错误消息是:

‘UserQuery.IsNull(object)’没有重载匹配委托’System.Func’

它适用于字符串数组,但不适用于int[] 。 这显然与拳击有关,但我想知道细节。

给出的答案(没有涉及值类型的差异)是正确的。 当其中一个变化类型参数是值类型时,协方差和逆变不起作用的原因如下。 假设它确实起作用,并显示事情是如何发生可怕的错误:

 Func f1 = ()=>123; Func f2 = f1; // Suppose this were legal. object ob = f2(); 

好的,会发生什么? f2与f1的引用相同。 因此无论f1做什么,f2都可以。 f1做什么? 它在堆栈上放置一个32位整数。 作业有什么作用? 它接受堆栈中的任何内容并将其存储在变量“ob”中。

拳击教学在哪里? 没有人! 我们只是将32位整数存储到期望不是整数的存储中,而是存储到包含盒装整数的堆位置的64位指针。 因此,您只是错误地对齐堆栈并使用无效引用损坏变量的内容。 很快这个过程就会火上浇油。

那么拳击教学应该去哪里? 编译器必须在某处生成装箱指令。 它不能在调用f2之后,因为编译器认为f2返回已经装箱的对象。 它不能进入​​f1的调用,因为f1返回一个int,而不是一个盒装的int。 它不能在f2的调用和f1的调用之间进行, 因为它们是同一个委托; 没有’之间’

我们在这里唯一能做的就是让第二行真正意味着:

 Func f2 = ()=>(object)f1(); 

现在我们不再在f1和f2之间有参考标识,那么方差点是什么? 协变参考转换的重点保留参考标识

无论你如何切片,事情都会出现严重错误,无法解决问题。 因此,最好的办法是首先使该function非法; generics委托类型不允许存在差异,其中值类型将是变化的东西。

更新:我应该在我的回答中注意到,在VB中,您可以将一个返回int的委托转换为一个返回对象的委托。 VB只生成第二个委托,它将调用包装到第一个委托并将结果装箱。 VB选择放弃引用转换保留对象标识的限制。

这说明了C#和VB的设计理念的一个有趣的区别。 在C#中,设计团队一直在思考“编译器如何找到可能是用户程序中的错误并引起他们注意?” VB团队正在思考“我们怎样才能弄清楚用户可能意味着什么,并代表他们这样做?” 简而言之,C#哲学是“如果你看到某些东西,说些什么”,那么VB哲学就是“做我的意思,而不是我所说的”。 两者都是完全合理的哲学; 有趣的是,由于设计原则,两种具有几乎相同特征集的语言在这些小细节中是如何不同的。

因为Int32是值类型而反对方差不适用于值类型。

你可以尝试这个:

 (new **object**[]{0,1,2,3}).Where(IsNull).Dump(); 

它不适用于int,因为没有对象。

尝试:

 void Fun() { IEnumerable objects = (new object[] { 0, 1, null, 3 }).Where(IsNull); foreach (object item in objects) { Console.WriteLine("item is null"); } } bool IsNull(object arg) { return arg == null; }