45 votes

Puzzling Enumerable.Cast InvalidCastException

Ce qui suit jette un InvalidCastException .

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

Pourquoi?

J'utilise Visual Studio 2008 SP1.

61voto

Matt Hamilton Points 98268

C'est très bizarre! Il y a un blog ici qui décrit la façon dont le comportement de l' Cast<T>() a été changé entre les deux .NET 3.5 et .NET 3.5 SP1, mais il n'explique toujours pas le InvalidCastException, qui vous obtenez même si vous réécrivez votre code ainsi:

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

Évidemment, vous pouvez le contourner en faisant la fonte de vous-même

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

Cela fonctionne, mais ça n'explique pas l'erreur dans la première place. J'ai essayé de coulée de la liste à court et flottent et ceux qui ont jeté la même exception.

Modifier

Ce billet de blog n'expliquer pourquoi ça ne marche pas!

Cast<T>() est une méthode d'extension sur IEnumerable plutôt que d' IEnumerable<T>. Cela signifie que chaque valeur en arrive au point où il est en fonte, il a déjà été en boîte de retour dans un Système.Objet. En substance, il essaie de faire ceci:

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

Ce code génère le InvalidCastException que vous obtenez. Si vous essayez de lancer un int directement à une longue que vous êtes bien, mais en jetant un coffret int remonte à une longue marche pas.

Certainement une curiosité!

27voto

Dmitriy Matveev Points 4680

La méthode Enumerable.Cast est définie comme suit:

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

Et comme il n'y a pas d'informations sur le type initial d'éléments de IEnumerable, je pense que chacune de vos ints est initialement convertie en System.Object via la boxe, après quoi elle a essayé d'être décompressée dans une variable longue, ce qui est incorrect.

Code similaire pour reproduire ceci:

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

La solution à votre problème sera donc:

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

3voto

Randolpho Points 36512

Hmm... de puzzle intéressant. Le plus intéressant étant donné que j'ai juste couru dans Visual Studio 2008 et il n'a pas le jeter à tous.

Je ne suis pas en utilisant le Service Pack 1, et vous peut-être, de sorte que pourrait être la question. Je sais qu'il y avait quelques "améliorations de performances" dans .Cast() dans la version SP1 qui pourrait être à l'origine du problème. Un peu de lecture:

L'Entrée De Blog 1

Blog D'Entrée 2

3voto

Triynko Points 5600

Je suis à nouveau!
Voici la solution à tous vos problèmes de conversion List<T> et Enumerable<T> . ~ 150 lignes de code
Assurez-vous simplement de définir au moins un opérateur de conversion explicite ou implicite pour les types d’entrée / sortie impliqués (s’il n’en existe pas), comme vous devriez le faire de toute façon!

 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 + "." );
    	}
    }
}
 

Exemple d'utilisation:

 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;}
    	}
    }
}
 

2voto

Triynko Points 5600

Je souhaite qu'ils auraient pu faire quelque chose d'intelligent, comme l'utilisation implicite ou explicite des opérateurs de transtypage définies pour le type. Le comportement actuel et l'incohérence est inacceptable. Absolument inutile dans son état actuel.

Après s'être rendu compte qu' Cast<Type> a été lancer une exception au lieu d'utiliser les opérateurs de cast que j'ai défini pour le type, je suis devenu irritable et trouvé ce fil. Si elle est définie pour l' IEnumerable, pourquoi n'auraient-ils pas simplement le mettre en œuvre pour utiliser la réflexion pour obtenir le type de l'objet, obtenir le type de cible, découvrez toute statique disponible opérateurs de conversion, et de trouver un approprié de faire le casting. Il pourrait lancer une hétérogènes IEnumerable en IEnumerable<T>.

La suite de la mise en œuvre est une idée...

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 + "." );
	}
}

Je peux ensuite appeler ce code avec succès:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X