Следующее выдает a InvalidCastException
.
IEnumerable<int> list = new List<int>() { 1 };
IEnumerable<long> castedList = list.Cast<long>();
Console.WriteLine(castedList.First());
Почему?
Я использую Visual Studio 2008 SP1.
Следующее выдает a InvalidCastException
.
IEnumerable<int> list = new List<int>() { 1 };
IEnumerable<long> castedList = list.Cast<long>();
Console.WriteLine(castedList.First());
Почему?
Я использую Visual Studio 2008 SP1.
Это очень странно! Там есть сообщение в блоге здесь, в котором описывается, как было изменено поведение Cast<T>()
между .NET 3.5 и .NET 3.5 SP1, но оно все еще не работает, 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);
Это работает, но это не объясняет ошибку в первую очередь. Я попробовал сделать список коротким и плавать, и те бросили то же исключение.
Edit
Это сообщение в блоге объясняет, почему он не работает!
Cast<T>()
- это метод расширения на IEnumerable
, а не IEnumerable<T>
. Это означает, что к моменту, когда каждое значение дойдет до того момента, когда оно будет запущено, оно уже помещено в блок System.Object. По существу это пытается сделать это:
int i = 1;
object o = i;
long l = (long)o;
Этот код генерирует InvalidCastException, которое вы получаете. Если вы попытаетесь наложить int прямо на длинный, вы в порядке, но отбрасывание коробочного int back в long не работает.
Конечно, странность!
Метод Enumerable.Cast определяется следующим образом:
public static IEnumerable<TResult> Cast<TResult>(
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(), которые могут вызвать проблему. Некоторое чтение:
Я снова на нем!
Здесь вы найдете решение всех проблем с преобразованием 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 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;}
}
}
}
Хотелось бы, чтобы они сделали что-то умное, например, используя любые неявные или явные операторы приведения, определенные для типа. Нынешнее поведение и несогласованность неприемлемы. Абсолютно бесполезно в текущем состоянии.
Поняв, что 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>();
Вот о чем подумать...
List<T>
или IEnumerable<T>
. IEnumerable<T>
, вы хотите, чтобы приведение/преобразование было применено лениво (т.е. литье/преобразование фактически не произойдет до тех пор, пока итератор не достигнет каждого элемента)? Полезное различие между cast/convert, поскольку оператор литья часто включает в себя создание нового объекта и может считаться конверсией:
Реализации "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);
Проблемные реализации "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>();
Проблемные реализации "Преобразовать"
//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 должны включать определенные явные операторы преобразования или позволить указать делегат преобразования. Спецификация языка С# для операторов преобразования - в частности, отсутствие имени метода - затрудняет получение делегата, за исключением отражения. Альтернативой является инкапсуляция или копирование кода конверсии, что лишний раз увеличивает (поддержание) сложность вашего кода, поскольку действительно возможные/разрешенные преобразования неявны в присутствии или отсутствии операторов преобразования и должны обрабатываться компилятором. Нам не нужно вручную искать определения с заглавными именами (например, "op_Explicit" ) соответствующих операторов преобразования с отражением в ВРЕМЕНИ РАБОТЫ для соответствующих типов. Кроме того, методы Cast/Convert для преобразования массовых/списков с использованием явных операторов преобразования должны действительно быть функцией структуры, а с помощью List.ConvertAll<T>
они... кроме спецификации языка затрудняет получение делегата для операторов преобразования эффективно!!!
Конечно, разумная вещь - использовать 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>
.