令人费解的Enumerable.Cast InvalidCastException

时间:2009-01-15 02:47:14

标签: c# .net exception

以下内容会引发InvalidCastException

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

为什么?

我正在使用Visual Studio 2008 SP1。

7 个答案:

答案 0 :(得分:59)

这很奇怪!有一篇博客文章here描述了{3.5}和.NET 3.5 SP1之间Cast<T>()的行为是如何改变的,但它仍然没有解释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);

这样可行,但它首先没有解释错误。我尝试将列表转换为short和float,然后抛出相同的异常。

修改

该博客文章确实解释了为什么它不起作用!

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

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

此代码抛出您正在获取的InvalidCastException。如果你试图直接将一个int转换为long,那么将一个盒装int强制转换为long也不行。

当然奇怪!

答案 1 :(得分:27)

Enumerable.Cast方法定义如下:

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

并且没有关于IEnumerable项目的初始类型的信息,所以我认为你的每个int最初都是通过装箱转换为System.Object,然后尝试将其解包为长变量,这是不正确的。

重现此类似的代码:

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

因此,您的问题的解决方案将是:

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

答案 2 :(得分:3)

嗯......有趣的谜题。更有意思的是,我刚刚在Visual Studio 2008中运行它并且它根本没有抛出。

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

Blog Entry 1

Blog Entry 2

答案 3 :(得分:3)

我又来了! 以下是您的所有List<T>Enumerable<T>转换问题的解决方案。 ~150行代码
只要确保为所涉及的输入/输出类型定义至少一个显式或隐式转换运算符(如果不存在),就像你应该做的那样!

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 (i.e. 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;}
        }
    }
}

答案 4 :(得分:2)

我希望他们可以做一些聪明的事情,比如使用为该类型定义的任何隐式或显式强制转换运算符。目前的行为和不一致是不可接受的。在当前条件下绝对没用。

在意识到Cast<Type>抛出异常而不是使用我为该类型定义的强制转换操作符后,我变得烦躁并找到了这个帖子。如果它是为IEnumerable定义的,为什么他们不会实现它来使用反射来获取对象的类型,获取目标类型,发现任何可用的静态转换运算符,并找到适当的执行转换。它可以将异构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>();

答案 5 :(得分:2)

以下是需要考虑的事情......

  1. 你想演员或转换吗?
  2. 您希望结果为List<T>还是IEnumerable<T>
  3. 如果结果是IEnumerable<T>,您是否希望延迟应用转换/转换(即,在迭代器到达每个元素之前,实际上不会发生转换/转换)?
  4. 强制转换/转换之间的区别,因为转换操作符通常涉及构建新对象,并且可以被视为转换:
    “Cast”实现应自动应用为所涉及类型定义的转换运算符; 可以构建或不构建新对象 “转换”实现应该允许指定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);
    

    使用现有框架的有问题的“强制转换”实施;假设您要将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 )
        )
    );
    

    <强>要点:
    转换/转换方法应包含已定义的显式转换运算符,或允许指定转换委托。转换运算符的C#语言规范 - 特别是缺少方法名称 - 使得除了通过反射之外很难获得委托。另一种方法是封装或复制转换代码,不必要地增加代码的(维护)复杂性,因为实际上,可能/允许转换是在转换运算符存在与否的情况下隐含的,应该是由编译器处理。我们不应该在所涉及的类型上手动搜索具有 RUN TIME 反射的适当转换运算符的加密命名定义(例如“op_Explicit”)。此外,使用显式转换运算符进行批量/列表转换的转换/转换方法实际上应该是一个框架功能,而使用List.ConvertAll<T>,它们是......除了语言规范使得很难获得转换运算符的委托有效地!!!

答案 6 :(得分:1)

当然,理所当然的事情是使用Select(i => (long)i),这就是我建议的内置值类型之间的转换和用户定义的转换。

但就像好奇一样,自.NET 4以来,您可以制作自己的扩展方法,该方法也适用于这些类型的转换。但它要求您愿意使用dynamic关键字。它就像这样:

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

正如我所说,可以使用积分转换(缩小或扩大转换),浮点类型之间/之间/之间的数值转换以及implicit operatorexplicit operator种类的转换“方法”。

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