2010-11-04 71 views
12

这段代码不在LINQPad中编译。为什么不委托使用值类型的逆变函数?

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

static bool IsNull(object arg) { return arg == null; } 

编译器的错误信息是:

否过载关于 'UserQuery.IsNull(对象)' 匹配委托 'System.Func'

它为一个字符串数组,但对int[]不起作用。这显然与拳击有关,但我想知道细节。

+0

应该是'.Where(x => IsNull(x))'? – 2010-11-04 11:50:27

+0

@Joel Etherton:同样的事情(差不多)。 – leppie 2010-11-04 11:52:19

+0

尝试使'IsNull'通用。废话,这就是你问的:) – leppie 2010-11-04 11:53:21

回答

38

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

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哲学则是“尽我所指,而不是我说的”。两者都是完全合理的哲学;有趣的是,由于设计原理,看到两种具有几乎相同特征集的语言在这些小细节上有所不同。

0

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

因为Int32是值类型和反方不会对价值类型的工作。

你可以试试这个:

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

转储()可能是一个扩展方法 – Konstantin 2010-11-04 12:18:29

相关问题