代码之家  ›  专栏  ›  技术社区  ›  thorn0

为什么委托反差不适用于值类型?

  •  14
  • thorn0  · 技术社区  · 14 年前

    此代码段未在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[] . 这显然与拳击有关,但我想知道细节。

    3 回复  |  直到 10 年前
        1
  •  41
  •   Eric Lippert    10 年前

    给出的答案(没有涉及值类型的方差)是正确的。当变量类型参数之一为值类型时,协变和逆变不起作用的原因如下。假设它确实起了作用,并显示出事情是如何严重出错的:

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

    好吧,怎么了?F2与F1相同。所以不管f1做什么,f2做什么。F1是做什么的?它在堆栈中放入一个32位整数。任务是什么?它获取堆栈中的任何内容,并将其存储在变量“ob”中。

    拳击教学在哪里? 没有一个!我们刚刚将一个32位整数存储到存储中,该存储不需要整数,而是一个指向包含装箱整数的堆位置的64位指针。所以,您只是将堆栈放错了位置,并用一个无效的引用破坏了变量的内容。很快,这一过程就会付之一炬。

    那么拳击教学应该去哪里呢?编译器必须在某处生成装箱指令。它不能在对F2的调用之后执行,因为编译器认为F2返回一个已经装箱的对象。它不能进入对f1的调用,因为f1返回一个in t,而不是一个装箱的int。它不能在对f1的调用和对f1的调用之间切换 因为他们是同一个委托人;没有“中间人” .

    我们唯一能做的就是让第二行实际上意味着:

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

    现在我们不再有f1和f2之间的引用恒等式了,所以 什么是差异点 ?共变的全部点 参考转换 是为了 保留引用标识 .

    不管你怎么切,事情都会大错特错,没有办法解决。因此,最好的做法是首先将特性设置为非法的;对于值类型将是变化的一般委托类型,不允许有任何差异。

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

    这说明了C和VB设计理念的有趣差异。在C中,设计团队总是在思考“编译器如何找到用户程序中可能存在的错误并引起他们的注意?”而vb团队正在思考“我们如何才能弄清楚用户可能要发生的事情,并代表他们去做?”简而言之,C哲学是“如果你看到什么,就说什么”,而VB哲学是“做我想做的,而不是我说的”。两者都是完全合理的哲学;有趣的是,由于设计原则,两种具有几乎相同特性集的语言在这些小细节上是如何不同的。

        2
  •  3
  •   Cyril Gandon niktrs    11 年前

    因为 Int32 是值类型,而反向差异不适用于值类型。

    你可以试试这个:

    (new **object**[]{0,1,2,3}).Where(IsNull).Dump();
    
        3
  •  -1
  •   Cyril Gandon niktrs    11 年前

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

    尝试:

    void Fun()
    {
        IEnumerable<object> 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; }