令人费解的Enumerable.Cast InvalidCastException

以下将引发一个InvalidCastException

 IEnumerable<int> list = new List<int>() { 1 }; IEnumerable<long> castedList = list.Cast<long>(); Console.WriteLine(castedList.First()); 

为什么?

我正在使用Visual Studio 2008 SP1。

这很奇怪! 这里有一篇博文,描述了.NET 3.5和.NET 3.5 SP1之间如何改变Cast<T>()的行为,但是它仍然没有解释InvalidCastException,如果你重写了你的代码,你甚至会得到这个InvalidCastException:

 var list = new[] { 1 }; var castedList = from long l in list select l; Console.WriteLine(castedList.First()); 

很明显,你可以通过自己动手来解决这个问题

 var castedList = list.Select(i => (long)i); 

这个工作,但它并没有解释错误的第一位。 我试着把这个列表简单地拖放,然后抛出相同的exception。

编辑

那篇博文解释了为什么它不起作用!

Cast<T>()IEnumerable上的扩展方法,而不是IEnumerable<T> 。 这意味着当每个值到达被投射的时候,它已经被装箱回到了System.Object。 本质上它试图做到这一点:

 int i = 1; object o = i; long l = (long)o; 

这段代码抛出InvalidCastException。 如果你试图将一个int直接投射到一个很长的状态,那么把一个盒装的int转换回一个long是行不通的。

当然是一个奇怪的!

Enumerable.Cast方法定义如下:

 public static IEnumerable<TResult> Cast<TResult>( this IEnumerable source ) 

并没有有关IEnumerable的项目的初始types的信息,所以我认为你的每个整数最初通过装箱转换为System.Object,然后试图将其拆箱到长variables,这是不正确的。

类似的代码来重现这一点:

 int i = 1; object o = i; // boxing long l = (long)o; // unboxing, incorrect // long l = (int)o; // this will work 

所以你的问题的解决scheme将是:

 ints.Select(i => (long)i) 

嗯…有趣的谜题。 所有更有趣的,因为我只是在Visual Studio 2008中运行它,它并没有抛出。

我没有使用Service Pack 1,您可能会这样,所以可能是问题所在。 我知道SP1发行版中的.Cast()中有一些“性能增强”,可能会导致此问题。 一些阅读:

博客条目1

博客条目2

我又来了!
这里是所有List<T>Enumerable<T>转换问题的解决scheme。 〜150行代码
只要确定为所涉及的input/输出types(如果不存在)定义至less一个显式或隐式转换运算符,就像您应该做的那样!

 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace System.Collections.Generic //purposely in same namespace as List<T>,IEnumerable<T>, so extension methods are available with them { public static class Enumerable { public static List<TOutput> ConvertAll<TInput,TOutput>( this IEnumerable<TInput> input ) { return BuildConvertedList<TInput,TOutput>( input, GetConverterDelegate<TInput,TOutput>() ); } public static IEnumerable<TOutput> ConvertAll<TInput,TOutput>( this IEnumerable<TInput> input, bool lazy ) { if (lazy) return new LazyConverter<TInput,TOutput>( input, GetConverterDelegate<TInput,TOutput>() ); return BuildConvertedList<TInput,TOutput>( input, GetConverterDelegate<TInput,TOutput>() ); } public static List<TOutput> ConvertAll<TInput,TOutput>( this IEnumerable<TInput> input, Converter<TInput, TOutput> converter ) { return BuildConvertedList<TInput,TOutput>( input, converter ); } public static List<TOutput> ConvertAll<TInput, TOutput>( this List<TInput> input ) { Converter<TInput, TOutput> converter = GetConverterDelegate<TInput,TOutput>(); return input.ConvertAll<TOutput>( converter ); } public static IEnumerable<TOutput> ConvertAll<TInput, TOutput>( this List<TInput> input, Converter<TInput, TOutput> converter, bool lazy ) { if (lazy) return new LazyConverter<TInput, TOutput>( input, converter ); return input.ConvertAll<TOutput>( converter ); } public static List<TOutput> ConvertAll<TInput, TOutput>( this List<TInput> input, Converter<TInput, TOutput> converter ) { return input.ConvertAll<TOutput>( converter ); } //Used to manually build converted list when input is IEnumerable, since it doesn't have the ConvertAll method like the List does private static List<TOutput> BuildConvertedList<TInput,TOutput>( IEnumerable<TInput> input, Converter<TInput, TOutput> converter ){ List<TOutput> output = new List<TOutput>(); foreach (TInput input_item in input) output.Add( converter( input_item ) ); return output; } private sealed class LazyConverter<TInput, TOutput>: IEnumerable<TOutput>, IEnumerator<TOutput> { private readonly IEnumerable<TInput> input; private readonly Converter<TInput, TOutput> converter; private readonly IEnumerator<TInput> input_enumerator; public LazyConverter( IEnumerable<TInput> input, Converter<TInput, TOutput> converter ) { this.input = input; this.converter = converter; this.input_enumerator = input.GetEnumerator(); } public IEnumerator<TOutput> GetEnumerator() {return this;} //IEnumerable<TOutput> Member IEnumerator IEnumerable.GetEnumerator() {return this;} //IEnumerable Member public void Dispose() {input_enumerator.Dispose();} //IDisposable Member public TOutput Current {get {return converter.Invoke( input_enumerator.Current );}} //IEnumerator<TOutput> Member object IEnumerator.Current {get {return Current;}} //IEnumerator Member public bool MoveNext() {return input_enumerator.MoveNext();} //IEnumerator Member public void Reset() {input_enumerator.Reset();} //IEnumerator Member } private sealed class TypeConversionPair: IEquatable<TypeConversionPair> { public readonly Type source_type; public readonly Type target_type; private readonly int hashcode; public TypeConversionPair( Type source_type, Type target_type ) { this.source_type = source_type; this.target_type = target_type; //precalc/store hash, since object is immutable; add one to source hash so reversing the source and target still produces unique hash hashcode = (source_type.GetHashCode() + 1) ^ target_type.GetHashCode(); } public static bool operator ==( TypeConversionPair x, TypeConversionPair y ) { if ((object)x != null) return x.Equals( y ); if ((object)y != null) return y.Equals( x ); return true; //x and y are both null, cast to object above ensures reference equality comparison } public static bool operator !=( TypeConversionPair x, TypeConversionPair y ) { if ((object)x != null) return !x.Equals( y ); if ((object)y != null) return !y.Equals( x ); return false; //x and y are both null, cast to object above ensures reference equality comparison } //TypeConversionPairs are equal when their source and target types are equal public bool Equals( TypeConversionPair other ) { if ((object)other == null) return false; //cast to object ensures reference equality comparison return source_type == other.source_type && target_type == other.target_type; } public override bool Equals( object obj ) { TypeConversionPair other = obj as TypeConversionPair; if ((object)other != null) return Equals( other ); //call IEqualityComparer<TypeConversionPair> implementation if obj type is TypeConversionPair return false; //obj is null or is not of type TypeConversionPair; Equals shall not throw errors! } public override int GetHashCode() {return hashcode;} //assigned in constructor; object is immutable } private static readonly Dictionary<TypeConversionPair,Delegate> conversion_op_cache = new Dictionary<TypeConversionPair,Delegate>(); //Uses reflection to find and create a Converter<TInput, TOutput> delegate for the given types. //Once a delegate is obtained, it is cached, so further requests for the delegate do not use reflection* //(*the typeof operator is used twice to look up the type pairs in the cache) public static Converter<TInput, TOutput> GetConverterDelegate<TInput, TOutput>() { Delegate converter; TypeConversionPair type_pair = new TypeConversionPair( typeof(TInput), typeof(TOutput) ); //Attempt to quickly find a cached conversion delegate. lock (conversion_op_cache) //synchronize with concurrent calls to Add if (conversion_op_cache.TryGetValue( type_pair, out converter )) return (Converter<TInput, TOutput>)converter; //Get potential conversion operators (target-type methods are ordered first) MethodInfo[][] conversion_op_sets = new MethodInfo[2][] { type_pair.target_type.GetMethods( BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy ), type_pair.source_type.GetMethods( BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy ) }; //Find appropriate conversion operator, //favoring operators on target type in case functionally equivalent operators exist, //since the target type's conversion operator may have access to an appropriate constructor //or a common instance cache (ie immutable objects may be cached and reused). for (int s = 0; s < conversion_op_sets.Length; s++) { MethodInfo[] conversion_ops = conversion_op_sets[s]; for (int m = 0; m < conversion_ops.Length; m++) { MethodInfo mi = conversion_ops[m]; if ((mi.Name == "op_Explicit" || mi.Name == "op_Implicit") && mi.ReturnType == type_pair.target_type && mi.GetParameters()[0].ParameterType.IsAssignableFrom( type_pair.source_type )) //Assuming op_Explicit and op_Implicit always have exactly one parameter. { converter = Delegate.CreateDelegate( typeof(Converter<TInput, TOutput>), mi ); lock (conversion_op_cache) //synchronize with concurrent calls to TryGetValue conversion_op_cache.Add( type_pair, converter ); //Cache the conversion operator reference for future use. return (Converter<TInput, TOutput>)converter; } } } return (TInput x) => ((TOutput)Convert.ChangeType( x, typeof(TOutput) )); //this works well in the absence of conversion operators for types that implement IConvertible //throw new InvalidCastException( "Could not find conversion operator to convert " + type_pair.source_type.FullName + " to " + type_pair.target_type.FullName + "." ); } } } 

样品使用:

 using System; using System.Collections.Generic; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { List<string> list = new List<string>(new string[] { "abcde", "abcd", "abc"/*will break length constraint*/, "ab", "a" }); //Uncomment line below to see non-lazy behavior. All items converted before method returns, and will fail on third item, which breaks the length constraint. //List<ConstrainedString> constrained_list = list.ConvertAll<string,ConstrainedString>(); IEnumerable<ConstrainedString> constrained_list = list.ConvertAll<string,ConstrainedString>( true ); //lazy conversion; conversion is not attempted until that item is read foreach (ConstrainedString constrained_string in constrained_list) //will not fail until the third list item is read/converted System.Console.WriteLine( constrained_string.ToString() ); } public class ConstrainedString { private readonly string value; public ConstrainedString( string value ){this.value = Constrain(value);} public string Constrain( string value ) { if (value.Length > 3) return value; throw new ArgumentException("String length must be > 3!"); } public static explicit operator ConstrainedString( string value ){return new ConstrainedString( value );} public override string ToString() {return value;} } } } 

我希望他们可以做一些聪明的事情,比如使用为types定义的任何隐式或显式的转换操作符。 目前的行为和不一致是不可接受的。 目前的情况绝对没用。

在意识到Cast<Type>抛出一个exception而不是使用我为这个types定义的转换操作符之后,我变得恼火并且发现了这个线程。 如果它是为IEnumerable定义的,为什么不实现它来使用reflection来获取对象的types,获取目标types,发现任何可用的静态转换运算符,并find合适的转换操作。 它可以将一个异构IEnumerable转换为一个IEnumerable<T>

下面的实现是一个工作的想法…

 public static class EnumerableMinusWTF { public static IEnumerable<TResult> Cast<TResult,TSource>(this IEnumerable<TSource> source) { Type source_type = typeof(TSource); Type target_type = typeof(TResult); List<MethodInfo> methods = new List<MethodInfo>(); methods.AddRange( target_type.GetMethods( BindingFlags.Static | BindingFlags.Public ) ); //target methods will be favored in the search methods.AddRange( source_type.GetMethods( BindingFlags.Static | BindingFlags.Public ) ); MethodInfo op_Explicit = FindExplicitConverstion(source_type, target_type, methods ); List<TResult> results = new List<TResult>(); foreach (TSource source_item in source) results.Add((TResult)op_Explicit.Invoke(null, new object[] { source_item })); return results; } public static MethodInfo FindExplicitConverstion(Type source_type, Type target_type, List<MethodInfo> methods) { foreach (MethodInfo mi in methods) { if (mi.Name == "op_Explicit") //will return target and take one parameter if (mi.ReturnType == target_type) if (mi.GetParameters()[0].ParameterType == source_type) return mi; } throw new InvalidCastException( "Could not find conversion operator to convert " + source_type.FullName + " to " + target_type.FullName + "." ); } } 

然后我可以成功地调用这个代码:

  //LessonID inherits RegexConstrainedString, and has explicit conversion operator defined to convert string to LessonID List<string> lessons = new List<String>(new string[] {"l001,l002"}); IEnumerable<LessonID> constrained_lessons = lessons.Cast<LessonID, string>(); 

这里有一些事情要考虑…

  1. 你想转换还是转换?
  2. 你想要的结果作为一个List<T>或一个IEnumerable<T>
  3. 如果结果是一个IEnumerable<T> ,你是否想要强制应用cast / convert(也就是说,在迭代器到达每个元素之前,cast / convert将不会真正发生)?

铸造/转换之间有用的区别,因为铸造操作员通常涉及构build新的对象,并可以被认为是一种转换:
“投射”实现应该自动应用为相关types定义的转换运算符; 一个新的对象可能会或可能不会被构build
“转换”实现应该允许指定一个System.Converter<TInput,TOutput>委托。

潜在的方法头文件:

 List<TOutput> Cast<TInput,TOutput>(IEnumerable<TInput> input); List<TOutput> Convert<TInput,TOutput>(IEnumerable<TInput> input, Converter<TInput,TOutput> converter); IEnumerable<TOutput> Cast<TInput,TOutput>(IEnumerable<TInput> input); IEnumerable<TOutput> Convert<TInput,TOutput>(IEnumerable<TInput> input, Converter<TInput,TOutput> converter); 

使用现有框架存在问题的“Cast”实现; 假设你作为input传递一个List<string> ,你想用前面的任何方法进行转换。

 //Select can return only a lazy read-only iterator; also fails to use existing explicit cast operator, because such a cast isn't possible in c# for a generic type parameter (so says VS2008) list.Select<TInput,TOutput>( (TInput x) => (TOutput)x ); //Cast fails, unless TOutput has an explicit conversion operator defined for 'object' to 'TOutput'; this confusion is what lead to this topic in the first place list.Cast<TOuput>(); 

有问题的“转换”实现

 //Again, the cast to a generic type parameter not possible in c#; also, this requires a List<T> as input instead of just an IEnumerable<T>. list.ConvertAll<TOutput>( new Converter<TInput,TOuput>( (TInput x) => (TOutput)x ) ); //This would be nice, except reflection is used, and must be used since c# hides the method name for explicit operators "op_Explicit", making it difficult to obtain a delegate any other way. list.ConvertAll<TOutput>( (Converter<TInput,TOutput>)Delegate.CreateDelegate( typeof(Converter<TInput,TOutput>), typeof(TOutput).GetMethod( "op_Explicit", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public ) ) ); 

概要:
Cast / Convert方法应该包含定义的显式转换运算符,或者允许指定一个转换委托。 C#转换运算符的语言规范 – 特别是缺less方法名称 – 使得除了通过reflection之外,很难获得委托。 另一种方法是封装或复制转换代码,不必要地增加代码的(维护)复杂性,因为在存在或不存在转换运算符的情况下, 可能/允许的转换是隐含的,应该由编译器处理。 我们不必手动search适当的转换运算符的cryptically-named定义(例如“op_Explicit”),并在运行时反映所涉及的types。 此外,使用显式转换运算符进行批量/列表转换的Cast / Convert方法应该是一个框架function,并且使用List.ConvertAll<T> ,它们是…除了语言规范使得难以获得转换的委托运营商效率!

当然,理智的做法是使用Select(i => (long)i) ,这就是我推荐的内置值types转换和用户定义转换之间的转换。

但是,正如一个奇怪的评论,自从.NET 4以来,有可能使你自己的扩展方法也适用于这种types的转换。 但是它要求你愿意使用dynamic关键字。 它是这样的:

 public static IEnumerable<TResult> CastSuper<TResult>(this IEnumerable source) { foreach (var s in source) yield return (TResult)(dynamic)s; } 

正如我所说过的那样,整数转换(缩小或扩大转换),浮点types之间的数值转换,以及implicit operatorexplicit operatortypes的转换“方法”。

当然,它仍然适用于原始的System.Enumerable.Cast<TResult>类的旧的引用转换和拆箱转换。