Следующее вызывает ошибку InvalidCastException.
IEnumerable<int> list = new List<int>() { 1 };
IEnumerable<long> castedList = list.Cast<long>();
Console.WriteLine(castedList.First());
Почему?
Я использую Visual Studio 2008 SP1.
@Timwi: Извините, но нет. Проблема в боксе, и что (long)o, где o - это коробочный int, выбросит. Если бы как-то здесь избегали бокса, этого бы не бросили. Кроме того, приведение - это языковая конструкция, а преобразование - это поведение среды выполнения, вызываемое этой конструкцией.
Вы не можете использовать .Cast<T> для преобразования между различными целочисленными типами (бросает new int[] { 1 }.Cast<long>()), а также не можете использовать его для вызова явных операторов преобразования (new XAttribute[] { new XAttribute("X", "Y") }.Cast<string>() бросает, хотя (string) new XAttribute("X", "Y") этого не делает). Даже если бокс объясняет первое (чего нет - там нет нужно для бокса; если внутренняя реализация использует его, то делает это бесплатно), он не объясняет второе (а это все ссылочные типы).
Кроме того, будьте осторожны, когда используете слово «языковая конструкция» в двух разных значениях. Конечно, существует приведение синтаксис, которое определяется языком и одинаково для всех трех операций, но спецификация языка также определяет семантика этого синтаксиса, и в нем четко указано, что «числовые преобразования», «неявное / явное преобразование операторы »и« преобразование ссылок »(приведение типов) - это три отдельные операции.





Это очень странно! Есть сообщение в блоге здесь, в котором описывается, как поведение Cast<T>() было изменено между .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<T>() - это метод расширения на IEnumerable, а не на IEnumerable<T>. Это означает, что к тому времени, когда каждое значение достигает точки, в которой оно было приведено, оно уже было помещено обратно в System.Object. По сути, он пытается сделать это:
int i = 1;
object o = i;
long l = (long)o;
Этот код создает исключение InvalidCastException, которое вы получаете. Если вы попытаетесь преобразовать int непосредственно в long, у вас все в порядке, но приведение упакованного int обратно в long не сработает.
Конечно странность!
+1 - У меня была именно эта проблема, я читал блог и не мог решить ее. Ваше редактирование прояснило это. Большой!
В этом нет ничего странного. .Cast<T> выполняет В ролях. Преобразование int в long не актерский состав. Очень жаль, что C# решил использовать один и тот же синтаксис для разных операций.
@Timwi: Извините, но нет. См. Мой комментарий к вашему комментарию выше.
@ Джейсон: Извини, но да. Жалко, что вы так агрессивно отреагируете, если не понимаете сути проблемы. Вы даже не ответили на мой комментарий о явных операторах преобразования.
Если кому-то интересно, могут ли они написать свой собственный метод Cast<T>, чтобы «исправить» это, см. Мой недавний ответ.
Enumerable.Cast обрабатывает все как System.Object (поскольку входная последовательность не является общей, а IEnumerable), поэтому упаковывает ее, если это тип значения. Вот объяснение того, почему C# не позволяет преобразовать упакованный тип значения в другой тип, к которому неупакованное значение могло быть преобразовано: blogs.msdn.microsoft.com/ericlippert/2009/03/19/…Хм ... интересная загадка. Тем более что интересно, что я только что запустил его в Visual Studio 2008 и он вообще не сделал выкинул.
Я не использую Service Pack 1, и вы, возможно, используете, так что это может быть проблемой. Я знаю, что были некоторые «улучшения производительности» в .Cast () в выпуске SP1, которые могли вызвать проблему. Некоторое чтение:
Да, было «исправлено» использование преобразования вместо преобразования в SP1 (согласно первому сообщению в блоге, на которое вы ссылались).
Метод 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)
Хотелось бы, чтобы они сделали что-то умное, например, использовали любые неявные или явные операторы приведения, определенные для типа. Текущее поведение и непоследовательность недопустимы. Совершенно бесполезен в нынешнем состоянии.
Поняв, что 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>();
Обратите внимание, что это совсем не помогает для преобразований, подобных тому, что было в исходном вопросе (от int до long), которые определены языком.
Действительно. Меня все больше раздражают .net и C#, потому что существует так много способов преобразования вещей, ни один из которых не использует очевидный путь применения определенных явных операторов преобразования. Кроме того, отражение кажется единственным способом применить явные операторы преобразования в общем виде.
Вот некоторые вещи, о которых стоит подумать ...
List<T> или IEnumerable<T>.IEnumerable<T>, хотите ли вы, чтобы приведение / преобразование применялось лениво (т.е. преобразование / преобразование фактически не произойдет, пока итератор не достигнет каждого элемента)?Полезное различие между приведением / преобразованием, поскольку оператор приведения часто включает в себя создание нового объекта и может считаться преобразованием:
Реализации "Cast" должны автоматически применять операторы преобразования, определенные для задействованных типов; новый объект может или не может быть построен.
Реализации "Convert" должны позволять указывать делегата 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" с использованием существующего фреймворка; Предположим, вы передаете в качестве входных данных 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>();
Проблемные реализации "Convert"
//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# для операторов преобразования, в частности отсутствие имени метода, затрудняет получение делегата, кроме как через отражение. Альтернативой является инкапсуляция или репликация кода преобразования, без необходимости увеличивая (обслуживающую) сложность вашего кода, поскольку на самом деле преобразования возможно / разрешено неявны при наличии или отсутствии операторов преобразования и должны обрабатываться компилятором. Нам не нужно вручную искать определения с загадочными именами (например, «op_Explicit») соответствующих операторов преобразования с отражением в ВРЕМЯ РАБОТЫ для задействованных типов. Кроме того, методы Cast / Convert для массовых преобразований / преобразований списков с использованием явных операторов преобразования действительно должны быть функцией фреймворка, а с List.ConvertAll<T> они ... кроме спецификации языка, затрудняющей эффективное получение делегата для операторов преобразования !!!
В разделе «Проблемные реализации преобразования» я использую typeof (TOutput) для получения MethodInfo оператора преобразования, но он может быть определен в typeof (TInput) или чем-либо еще, поэтому фактическая логика этого идеального подхода будет более сложной. Я хочу сказать, что это не должно быть так сложно и не должно выполняться во время выполнения.
Я снова за это!
Вот решение всех ваших проблем с преобразованием 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;}
}
}
}
Конечно, разумно использовать 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 operator и explicit operator.
И, конечно же, он по-прежнему работает со старыми добрыми преобразованиями эталонных файлов и преобразованиями распаковки, такими как оригинальный System.Enumerable.Cast<TResult>.
Ожидайте снижения производительности, если вы используете dynamic, поскольку анализ того, какое преобразование использовать, придется проводить во время выполнения. Вот почему рекомендуется .Select(i => (long)i).
Я удивлен, что ни один из ответов на самом деле не отвечает на вопрос Зачем. Ответ в том, что преобразование из int в long не актерский состав. Это преобразование. К сожалению, C# использует один и тот же синтаксис для обоих из них, так как это только сбивает людей с толку (по-видимому). Вы также не можете использовать
.Cast<T>()для вызова определенного пользователем явного оператора преобразования, потому что это тоже не приведение.