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

本文关键字:转换 泛型类型 运行时 | 更新日期: 2023-09-27 18:10:31

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

我的收藏是这样的:

new List<List<object>>() 

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

然而,由于协变/逆变(永远记不清是哪一个!(,我的ListList不是IEnumerableIEnumerable

我该怎么办?

我试过使用Convert.ChangeType,但它抱怨List不是IConvertible

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

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

好吧,根据道德大师的回答,我想出了这个。简单得令人震惊。

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

很简单。在这里写博客:当内部类型只在运行时已知时,抛出可枚举对象

我在TinyIoC中也遇到过类似的问题,我发现的"最干净"的解决方案不是"转换",而是使您的方法通用(因此是公共的IEnumerable'T Dostuf'T(((,然后使用您的运行时类型使用MakeGenericMethod调用它。它保持"干净",因为构建列表的实际方法只是像一个普通的通用方法一样操作,所以它不会被铸造等弄得一团糟。

如果看不到你的代码,很难知道这是否符合要求——以下是TinyDoc:中制作通用方法的相关部分

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

希望这是有意义的:(

  1. 将其非类型化为IEnumerable<IEnumerable>
  2. 使用反射调用一个函数,该函数使用具有适当TIEnumerable<IEnumerable<T>>
  3. 使用switch语句强制转换为适当的类型
  4. 使用dynamic


示例

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:如果您在运行时只知道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()缓存在某个位置。

我相信答案是"你不能"——尽管我可能被一些使用大量反射或直接发出IL之类的超级黑客代码证明是错的。

编译器和jiter需要知道所有东西的对象类型,以设置堆栈、正确分配内存等。

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

根据您的评论,

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

我收集到的是,尽管您可以投射内部列表,但您将要添加到外部列表的对象会出现差异问题。

基于这个链接,我所理解的是,您可以使用一种变通方法来实例化外部列表

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

但是如果你已经有了一个初始化的List<List<T>>,你只需要一个cast:

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