[英]How to cast a generic type at runtime in c#
当我在运行时只知道T
时,我需要创建一个IEnumerable<IEnumerable<T>>
。
我已经建立了我的收藏品:
new List<List<object>>()
其中内部列表中的所有对象都是T
然而,由于共同/逆转(永远不会记住它是什么!)我的List
List
不是IEnumerable
的IEnumerable
。
我该怎么办?
我已经尝试过使用Convert.ChangeType
但它认为List
不是IConvertible
线索:阅读问题。 再次。 我说我在运行时只知道T
好的,根据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;
}
简单。 在此博客: 在内部类型仅在运行时知道时,转换枚举
IEnumerable<IEnumerable>
T
的IEnumerable<IEnumerable<T>>
的函数 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 }
我与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);
}
希望有道理:)
编辑:如果您在运行时只知道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的超级hacky代码证明是错误的。
编译器和JIT'er需要知道所有对象类型,以便设置堆栈,正确分配内存等。
也许每种类型的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>>
你只需要一个演员:
list.Cast<IEnumerable<T>>();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.