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

时间:2010-11-04 11:46:37

标签: c# boxing value-type contravariance

此代码段未在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 个答案:

答案 0 :(得分:41)

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

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返回一个int,而不是一个盒装的int。它不能在调用f2和调用f1 之间进行,因为它们是同一个委托;没有'之间'

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

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

现在我们再也没有f1和f2之间的参考标识,所以方差点是什么?协变引用转换的重点是保留引用标识

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

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

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

答案 1 :(得分:3)

因为Int32是值类型而反对方差对值类型不起作用。

你可以尝试这个:

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

答案 2 :(得分:-1)

它不适用于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; }