Puzzling Enumerable.Cast InvalidCastException

Следующее генерирует InvalidCastException .

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

Зачем?

Я использую Visual Studio 2008 SP1.

Это очень странно! Здесь есть запись в блоге, в которой описывается, как изменилось поведение Cast() между .NET 3.5 и .NET 3.5 SP1, но оно по-прежнему не объясняет 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); 

Это работает, но это не объясняет ошибку в первую очередь. Я попробовал сделать список коротким и плавать, и те бросили то же исключение.

редактировать

Это сообщение в блоге объясняет, почему он не работает!

Cast() – это метод расширения для IEnumerable а не IEnumerable . Это означает, что к тому моменту, когда каждое значение дойдет до того момента, когда оно будет запущено, оно снова помещено в объект System.Object. По сути, он пытается это сделать:

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

Этот код генерирует InvalidCastException, которое вы получаете. Если вы попытаетесь наложить int прямо на длинный, вы в порядке, но отбрасывание коробочного int back в long не работает.

Конечно, странность!

Метод Enumerable.Cast определяется следующим образом:

 public static IEnumerable Cast( this IEnumerable source ) 

И нет информации об исходном типе элементов IEnumerable, поэтому я думаю, что каждый из ваших ints сначала преобразовывается в 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) 

Хм … интересная головоломка. Тем интереснее, что я только что запустил его в Visual Studio 2008, и он не бросал вообще.

Я не использую Service Pack 1, и вы можете быть, так что это может быть проблемой. Я знаю, что в выпуске SP1 были выпущены некоторые улучшения производительности в .Cast (), которые могут вызвать проблему. Некоторые чтения:

Запись в блоге 1

Вступление в блог 2

Я снова в этом!
Вот решение для всех задач преобразования List и Enumerable . ~ 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,IEnumerable, so extension methods are available with them { public static class Enumerable { public static List ConvertAll( this IEnumerable input ) { return BuildConvertedList( input, GetConverterDelegate() ); } public static IEnumerable ConvertAll( this IEnumerable input, bool lazy ) { if (lazy) return new LazyConverter( input, GetConverterDelegate() ); return BuildConvertedList( input, GetConverterDelegate() ); } public static List ConvertAll( this IEnumerable input, Converter converter ) { return BuildConvertedList( input, converter ); } public static List ConvertAll( this List input ) { Converter converter = GetConverterDelegate(); return input.ConvertAll( converter ); } public static IEnumerable ConvertAll( this List input, Converter converter, bool lazy ) { if (lazy) return new LazyConverter( input, converter ); return input.ConvertAll( converter ); } public static List ConvertAll( this List input, Converter converter ) { return input.ConvertAll( 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 BuildConvertedList( IEnumerable input, Converter converter ){ List output = new List(); foreach (TInput input_item in input) output.Add( converter( input_item ) ); return output; } private sealed class LazyConverter: IEnumerable, IEnumerator { private readonly IEnumerable input; private readonly Converter converter; private readonly IEnumerator input_enumerator; public LazyConverter( IEnumerable input, Converter converter ) { this.input = input; this.converter = converter; this.input_enumerator = input.GetEnumerator(); } public IEnumerator GetEnumerator() {return this;} //IEnumerable 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 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 { 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 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 conversion_op_cache = new Dictionary(); //Uses reflection to find and create a Converter 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 GetConverterDelegate() { 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)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), 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)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 list = new List(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 constrained_list = list.ConvertAll(); IEnumerable constrained_list = list.ConvertAll( 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;} } } } 

Хотелось бы, чтобы они могли сделать что-то умное, например, используя любые неявные или явные операторы приведения, определенные для типа. Нынешнее поведение и несогласованность неприемлемы. Абсолютно бесполезно в его нынешнем состоянии.

Поняв, что Cast выбрасывал исключение вместо использования операторов трансляции, которые я определил для типа, я стал раздражать и нашел этот stream. Если он определен для IEnumerable , почему бы им просто не реализовать его, чтобы использовать reflection, чтобы получить тип объекта, получить целевой тип, обнаружить любые доступные операторы статического преобразования и найти подходящий для выполнения. Он может использовать гетерогенный IEnumerable в IEnumerable .

Следующая реализация – это работающая идея …

 public static class EnumerableMinusWTF { public static IEnumerable Cast(this IEnumerable source) { Type source_type = typeof(TSource); Type target_type = typeof(TResult); List methods = new List(); 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 results = new List(); 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 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 lessons = new List(new string[] {"l001,l002"}); IEnumerable constrained_lessons = lessons.Cast(); 

Вот некоторые вещи, о которых нужно подумать …

  1. Вы хотите отливать или конвертировать?
  2. Вы хотите, чтобы результат был как List и IEnumerable .
  3. Если результатом является IEnumerable , вы хотите, чтобы cast / convert применялся лениво (т. Е. Cast / convert фактически не произойдет до тех пор, пока iterator не достигнет каждого элемента)?

Полезное различие между cast / convert, поскольку оператор литья часто включает в себя построение нового объекта и может считаться конверсией:
Реализации «Cast» должны автоматически применять операторы преобразования, определенные для задействованных типов; новый объект может быть или не быть построен .
Реализации «Конвертировать» должны позволить указать делегат System.Converter .

Потенциальные заголовки методов:

 List Cast(IEnumerable input); List Convert(IEnumerable input, Converter converter); IEnumerable Cast(IEnumerable input); IEnumerable Convert(IEnumerable input, Converter converter); 

Проблемные реализации «Cast» с использованием существующей структуры; предположим, что вы передаете как ввод List , который вы хотите преобразовать с любым из предыдущих методов.

 //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 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(); 

Проблемные «конвертирующие» реализации

 //Again, the cast to a generic type parameter not possible in c#; also, this requires a List as input instead of just an IEnumerable. list.ConvertAll( new Converter( (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( (Converter)Delegate.CreateDelegate( typeof(Converter), typeof(TOutput).GetMethod( "op_Explicit", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public ) ) ); 

Резюме:
Методы Cast / Convert должны включать определенные явные операторы преобразования или позволить указать делегат преобразования. Спецификация языка C # для операторов преобразования – в частности, отсутствие имени метода – затрудняет получение делегата, за исключением отражения. Альтернативой является инкапсуляция или копирование кода конверсии, что лишний раз увеличивает (поддержание) сложность вашего кода, поскольку действительно возможные / разрешенные преобразования неявны в присутствии или отсутствии операторов преобразования и должны обрабатываться компилятором. Нам не нужно вручную искать критически названные определения (например, «op_Explicit») соответствующих операторов преобразования с reflectionм в RUN TIME по соответствующим типам. Кроме того, методы Cast / Convert для преобразования массового / списка с использованием явных операторов преобразования действительно должны быть функцией структуры, а с List.ConvertAll они … кроме спецификации языка затрудняет получение делегата для преобразования операторы!

Разумеется, разумная вещь – использовать Select(i => (long)i) и именно это я бы рекомендовал для конверсий между встроенными типами значений и для пользовательского преобразования.

Но как любопытное замечание, так как .NET 4 можно создать собственный метод расширения, который также работает с этими видами конверсий. Но для этого требуется, чтобы вы использовали dynamic ключевое слово. Это просто так:

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

Как я уже говорил, работает с интегральными преобразованиями (сужение или расширение преобразований), числовые преобразования в / из / между типами с плавающей точкой и методы «преобразования» типов implicit operator и explicit operator .

И, конечно же, он по-прежнему работает с хорошими старыми преобразованиями ссылок и распаковками конверсий, такими как оригинальная System.Enumerable.Cast .

  • C # - получить номер строки, в которой было исключение
  • Как я могу написать пользовательские исключения?
  • Android Studio IDE: перерыв на исключении
  • Что я должен знать о Structured Exceptions (SEH) в C ++?
  • Eclipse - java.lang.ClassNotFoundException
  • C ++: обрабатывать ресурсы, если конструкторы могут генерировать исключения (ссылка на часто задаваемые вопросы 17.4)
  • Как использовать try catch для обработки исключений - лучшая практика
  • Не удалось загрузить файл или сборку ... Параметр неверен
  • Почему существует NotImplementedException?
  • Устранение неполадок BadImageFormatException
  • Как реализовать один обработчик исключений «catch'em all» с резюме?
  • Давайте будем гением компьютера.