开发者

How to cast a generic type at runtime in c#

开发者 https://www.devze.com 2023-04-11 19:08 出处:网络
I need to create an IEnumerable<IEnumerable<T>> when I only know T at runtime. I have built up my collection like so:

I need to create an IEnumerable<IEnumerable<T>> when I only know T at runtime.

I have built up my collection like so:

new List<List<object>>() 

where all the objects in the inner list are a T

However because of co/contravariance (can never remember which it is!) my List of List开发者_如何学Gos isnt an IEnumerable of IEnumerables.

What can I do about this?

I've tried using Convert.ChangeType but it moans that List isn't IConvertible

Clue: Read the question. Again. I said I only know T at runtime.


OK, based on Master Morality's answer, I've come up with this. Shockingly simple.

public static IEnumerable Cast(this IEnumerable self, Type innerType)
{
    var methodInfo = typeof (Enumerable).GetMethod("Cast");
    var genericMethod = methodInfo.MakeGenericMethod(innerType);
    return genericMethod.Invoke(null, new [] {self}) as IEnumerable;
}

Simple. Blogged about it here: Casting an enumerable when the inner type is only known at runtime


I've had similar issues with TinyIoC, and rather than "converting", the "cleanest" solution I've found is to make your method generic (so public IEnumerable'T DoStuff'T()), then call that using MakeGenericMethod using your runtime type. It stays "clean" because your actual method that constructs the list just operates as if it is a normal generic method, so it doesn't get cluttered with casting etc.

Without seeing your code it's hard to know if that fits the bill - here's the relevant bits to make the generic method from TinyIoc:

public static class TypeExtensions
{
    private static SafeDictionary<GenericMethodCacheKey, MethodInfo> _genericMethodCache;

    static TypeExtensions()
    {
        _genericMethodCache = new SafeDictionary<GenericMethodCacheKey, MethodInfo>();
    }

    /// <summary>
    /// Gets a generic method from a type given the method name, binding flags, generic types and parameter types
    /// </summary>
    /// <param name="sourceType">Source type</param>
    /// <param name="bindingFlags">Binding flags</param>
    /// <param name="methodName">Name of the method</param>
    /// <param name="genericTypes">Generic types to use to make the method generic</param>
    /// <param name="parameterTypes">Method parameters</param>
    /// <returns>MethodInfo or null if no matches found</returns>
    /// <exception cref="System.Reflection.AmbiguousMatchException"/>
    /// <exception cref="System.ArgumentException"/>
    public static MethodInfo GetGenericMethod(this Type sourceType, System.Reflection.BindingFlags bindingFlags, string methodName, Type[] genericTypes, Type[] parameterTypes)
    {
        MethodInfo method;
        var cacheKey = new GenericMethodCacheKey(sourceType, methodName, genericTypes, parameterTypes);

        // Shouldn't need any additional locking
        // we don't care if we do the method info generation
        // more than once before it gets cached.
        if (!_genericMethodCache.TryGetValue(cacheKey, out method))
        {
            method = GetMethod(sourceType, bindingFlags, methodName, genericTypes, parameterTypes);
            _genericMethodCache[cacheKey] = method;
        }

        return method;
    }

    private static MethodInfo GetMethod(Type sourceType, BindingFlags bindingFlags, string methodName, Type[] genericTypes, Type[] parameterTypes)
    {
        var methods =
            sourceType.GetMethods(bindingFlags).Where(
                mi => string.Equals(methodName, mi.Name, StringComparison.InvariantCulture)).Where(
                    mi => mi.ContainsGenericParameters).Where(mi => mi.GetGenericArguments().Length == genericTypes.Length).
                Where(mi => mi.GetParameters().Length == parameterTypes.Length).Select(
                    mi => mi.MakeGenericMethod(genericTypes)).Where(
                        mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)).ToList();

        if (methods.Count > 1)
        {
            throw new AmbiguousMatchException();
        }

        return methods.FirstOrDefault();
    }

    private sealed class GenericMethodCacheKey
    {
        private readonly Type _sourceType;

        private readonly string _methodName;

        private readonly Type[] _genericTypes;

        private readonly Type[] _parameterTypes;

        private readonly int _hashCode;

        public GenericMethodCacheKey(Type sourceType, string methodName, Type[] genericTypes, Type[] parameterTypes)
        {
            _sourceType = sourceType;
            _methodName = methodName;
            _genericTypes = genericTypes;
            _parameterTypes = parameterTypes;
            _hashCode = GenerateHashCode();
        }

        public override bool Equals(object obj)
        {
            var cacheKey = obj as GenericMethodCacheKey;
            if (cacheKey == null)
                return false;

            if (_sourceType != cacheKey._sourceType)
                return false;

            if (!String.Equals(_methodName, cacheKey._methodName, StringComparison.InvariantCulture))
                return false;

            if (_genericTypes.Length != cacheKey._genericTypes.Length)
                return false;

            if (_parameterTypes.Length != cacheKey._parameterTypes.Length)
                return false;

            for (int i = 0; i < _genericTypes.Length; ++i)
            {
                if (_genericTypes[i] != cacheKey._genericTypes[i])
                    return false;
            }

            for (int i = 0; i < _parameterTypes.Length; ++i)
            {
                if (_parameterTypes[i] != cacheKey._parameterTypes[i])
                    return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            return _hashCode;
        }

        private int GenerateHashCode()
        {
            unchecked
            {
                var result = _sourceType.GetHashCode();

                result = (result * 397) ^ _methodName.GetHashCode();

                for (int i = 0; i < _genericTypes.Length; ++i)
                {
                    result = (result * 397) ^ _genericTypes[i].GetHashCode();
                }

                for (int i = 0; i < _parameterTypes.Length; ++i)
                {
                    result = (result * 397) ^ _parameterTypes[i].GetHashCode();
                }

                return result;
            }
        }
    }
}

Which is called as follows:

private object GetIEnumerableRequest(Type type)
{
    var genericResolveAllMethod = this.GetType().GetGenericMethod(BindingFlags.Public | BindingFlags.Instance, "ResolveAll", type.GetGenericArguments(), new[] { typeof(bool) });

    return genericResolveAllMethod.Invoke(this, new object[] { false });
}

And ResolveAll is defined as:

public IEnumerable<ResolveType> ResolveAll<ResolveType>()
    where ResolveType : class
{
    return ResolveAll<ResolveType>(true);
}

Hope that makes sense :)


  1. Use it untyped as IEnumerable<IEnumerable>
  2. Use reflection to call a function that takes a IEnumerable<IEnumerable<T>> with the appropriate T
  3. use a switch statement to cast to appropriate type
  4. use dynamic


Examples

static IEnumerable<IEnumerable<T>> castList<T>(List<List<object>> list) {
    return list.Select(x => x.Cast<T>());
}
void DoSomething(Type myT, List<List<object>> list) {
    object untyped = typeof(MyClass).GetMethod("castList")
        .MakeGenericMethod(myT)
        .Invoke(null, new[] { list });
    // untyped is an IEnumerable<IEnumerable<myT>> at runtime, 
    // but obviously you don't know that at compile time.

    // what can you do with untyped? 
    // 1: use it like an untyped container
    var option1 = (IEnumerable<IEnumerable>)untyped;
    foreach(var inner in option1)
        foreach(object item in inner)
            Console.WriteLine(object);
    // 2: pass it to a function that you reflect on using
    //    the above makeGenericMethod strategy
    typeof(MyClass).GetMethod("Process")
        .MakeGenericMethod(myT)
        .Invoke(null, new[] { untyped });
    // 3: Cast it conditionally
    switch(Type.GetTypeCode(myT)) {
        case TypeCode.Int32:
             Process((IEnumerable<IEnumerable<int>>)untyped);
             break;
        case TypeCode.Single:
             Process((IEnumerable<IEnumerable<float>>)untyped);
             break;
    }
    // 4: make it a dynamic
    dynamic dyn = untyped;
    Process(dyn);
}
static void Process<T>(IEnumerable<IEnumerable<T>> ienumerable) {
    Console.WriteLine("Processing type: {0}", typeof(T).Name);
    foreach(var inner in ienumerable)
        foreach(T item in inner)
            DoSomething(item); // item is now type T
}


Edit: If you only know T at run time, you could do it by building an expression. and compiling it. like so:

var listOfLists = new List<List<object>>();

//... do list building...

//types
var runTimeType = typeof(MyRuntimeType);
var innerListType = typeof(List<>)
    .MakeGenericType(typeof(object));
var innerEnumerableType = typeof(IEnumerable<>)
    .MakeGenericType(runTimeType);
var outerListType = typeof(List<>)
    .MakeGenericType(innerListType);

//methods
var castm = typeof(Enumerable).GetMethod("Cast")
    .MakeGenericMethod(runTimeType);
var selectm = typeof(Enumerable).GetMethods()
    .Where(x => x.Name == "Select").First()
    .MakeGenericMethod(innerListType, innerEnumerableType);

//expressions (parameters)
var innerParamx = Expression.Parameter(innerListType);
var outerParamx = Expression.Parameter(outerListType);

// listOfLists.Select(x => x.Cast<T>()); 
// as an expression
var castx = Expression.Call(castm, innerParamx);
var lambdax = Expression.Lambda(castx, innerParamx);
var selectx = Expression.Call(selectm, outerParamx, lambdax);
var lambdax2 = Expression.Lambda(selectx, outerParamx);

var result = lambdax2.Compile().DynamicInvoke(listOfLists);

you could optionally cached lambdax2.Compile() somewhere for each runtime type, performance.


I believe the answer is "you can't" - though I could probably be proven wrong with some super hacky code that uses a ton of reflection or emits IL directly or something.

The compiler and JIT'er needs to know the object types for everything to setup the stack, allocate memory properly, etc.

Perhaps every type T can implement some marker interface, or derive from a common base? Various behaviors can be implemented virtually. If you can comment a bit on what your program is trying to do, perhaps people could come up with a good design.


Based on your comment,

not exactly but thanks, i could create the inner list of the right type, but then i can push objects into it and i still have the variance issue

what I gather is that you get a variance issue on the objects that you are adding to the outer list although you are able to cast the inner list.

Based on this link, what I understand is that you can use a workaround to instantiate the outer list,

// Simple workaround for single method
// Variance in one direction only
public static void Add<S, D>(List<S> source, List<D> destination)
    where S : D
{
    foreach (S sourceElement in source)
    {
        destination.Add(sourceElement);
    }
}


public IEnumerable<IEnumerable<T>> void Test<T>()
{
  // Create a top IEnumeranble instance you should specify list element type
  var result = new List<IEnumerable<T>>();

  // Add an internal IEnumerable<T>
  result.Add(new List<T>());

  return result;
}

but if you already has an initialized List<List<T>> you just need a cast:

list.Cast<IEnumerable<T>>();
0

精彩评论

暂无评论...
验证码 换一张
取 消