2011-01-25 78 views
2

使用这种类型:为什么Enumerable.Cast <>使用我的转换运算符?

class Foo 
{ 
    public static implicit operator int(Foo obj) 
    { 
    return 5; 
    } 
} 

var test=new[] { new Foo() }; 

下按预期工作

var ok=test.Select(x => (int)x).ToList(); 

但使用铸铁<>失败,一个InvalidCastException - 为什么?

var fail=test.Cast<int>().ToList(); 
+1

看看这是你的问题:http://stackoverflow.com/questions/445471/puzzling-enumerable-cast-invalidcastexception/445497#445497 – 2011-01-25 08:11:23

回答

2

阅读Jon Skeet关于重新实现Linq(EduLinq)的博客,具体为part 33,他在那里他这说:

值得注意的是(从.NET 3.5 SP1)Cast和OfType只执行引用和拆箱转换。它们不会将装箱的int转换为long,或执行用户定义的转换。基本上它们遵循与从对象转换为泛型类型参数相同的规则。 (这对于实现非常方便!)

0

Enumerable.Cast的文档实际上有点含糊不清,并且谈论了关于转换和转换。但它会说“如果一个元素不能转换为输入TResult,这个方法将抛出一个异常”,而你的类Foo不能转换为int,但可以使用转换语法转换。后者是一种方法调用。

一般演员和工作OfType工作类似于“作为”一个“是”,如果你写:

var foo = new Foo() 
var bar = foo is int; 

酒吧是假的。 Cast看起来似乎是一致的(尽管MSDN上的文档并不完全)。当is-operator返回false时失败。 (有一种特殊情况,情况并非如此,如果序列中的值为空,并且T是参考类型)

1

铸造操作符纯粹是C#编译器级别的功能,运行时不知道任何关于它们的信息,所以没有简单的方法通过泛型Cast方法来实现此操作。要做到这一点的方法之一是执行运行时代码生成:

 

    public static class Converter<TSource, TResult> 
    { 
     static Converter() 
     { 
      var sourceParameter = Expression.Parameter(typeof(TSource)); 
      var conversionExpression = Expression.Lambda<Func<TSource, TResult>>(
       Expression.Convert(sourceParameter, typeof(TResult)), 
       sourceParameter); 

      Instance = conversionExpression.Compile(); 
     } 

     public static Func<TSource, TResult> Instance 
     { 
      get; 
      private set; 
     } 
    } 

    public static class EnumerableEx 
    { 
     public static IEnumerable<TResult> Cast<TSource, TResult>(this IEnumerable<TSource> source) 
     { 
      return source.Select(Converter<TSource, TResult>.Instance); 
     } 
    } 
 

但后来你会失去编译时检查:

 

var test = new[] { new Foo() }; 
var ok = test.Cast<Foo, int>().ToList(); // compiles and works ok 
var error = test.Cast<Foo, double>().ToList(); // compiles but fails at run-time 
 

另一种方式是在Puzzling Enumerable.Cast InvalidCastException但这种使用反射作为将不适用于从int到long的内置转换。