如何在运行时在c#中转换泛型类型

时间:2011-10-06 15:51:47

标签: c# generics reflection

当我在运行时只知道IEnumerable<IEnumerable<T>>时,我需要创建一个T

我已经建立了我的收藏品:

new List<List<object>>() 

其中内部列表中的所有对象都是T

然而,由于共同/逆转(永远不会记住它是什么!)List List的{​​{1}}不是IEnumerable的{​​{1}}。

我该怎么办?

我已尝试使用IEnumerable,但它认为Convert.ChangeType不是List

线索:阅读问题。再次。我说我在运行时只知道IConvertible

7 个答案:

答案 0 :(得分:15)

好的,根据Master Morality的回答,我想出了这个。令人震惊的简单。

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

简单。在此处发表了博客:Casting an enumerable when the inner type is only known at runtime

答案 1 :(得分:3)

  1. 使用无类型为IEnumerable<IEnumerable>
  2. 使用反射来调用带有IEnumerable<IEnumerable<T>> T
  3. dynamic的函数
  4. 使用switch语句转换为适当的类型
  5. 使用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 }
  6. <小时/> 实施例

    {{1}}

答案 2 :(得分:3)

我和TinyIoC有类似的问题,而不是“转换”,我发现的“最干净”的解决方案是让你的方法通用(所以公共IEnumerable'T DoStuff'T()),然后调用使用运行时类型使用MakeGenericMethod。它保持“干净”,因为构造列表的实际方法就像是一个普通的泛型方法一样运行,因此它不会因为强制转换而变得混乱。

如果没有看到您的代码,很难知道这是否符合要求 - 这是从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;
            }
        }
    }
}

其名称如下:

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

ResolveAll定义为:

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

希望有道理:)

答案 3 :(得分:2)

编辑:如果您在运行时只知道T,则可以通过构建表达式来实现。并编译它。像这样:

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

对于每种运行时类型,性能,您可以选择在某处缓存lambdax2.Compile()

答案 4 :(得分:0)

我相信答案是“你不能” - 虽然我可能会被一些使用大量反射或直接发射IL的超级hacky代码证明是错误的。

编译器和JIT'er需要知道所有设置堆栈的对象类型,正确分配内存等等。

也许每种类型的T都可以实现一些标记接口,或者从一个共同的基础派生?可以虚拟地实现各种行为。如果你可以对你的程序尝试做什么做一些评论,也许人们可以想出一个好的设计。

答案 5 :(得分:0)

根据您的评论,

  

不完全但是谢谢,我可以创建右边的内部列表   类型,但然后我可以将对象推入其中,我仍然有   差异问题

我收集的是,虽然您可以投射内部列表,但是您在添加到外部列表的对象上会出现差异问题。

基于this link,我理解的是您可以使用变通方法来实例化外部列表,

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

答案 6 :(得分:-2)

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

但如果您已经初始化List<List<T>>,则只需要演员:

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