如何在c#中的运行时强制转换泛型类型
本文关键字:转换 泛型类型 运行时 | 更新日期: 2023-09-27 18:10:31
当我在运行时只知道T
时,我需要创建一个IEnumerable<IEnumerable<T>>
。
我的收藏是这样的:
new List<List<object>>()
其中内部列表中的所有对象都是T
然而,由于协变/逆变(永远记不清是哪一个!(,我的List
的List
不是IEnumerable
的IEnumerable
。
我该怎么办?
我试过使用Convert.ChangeType
,但它抱怨List
不是IConvertible
线索:阅读问题。再一次我说我只在运行时知道T
。
好吧,根据道德大师的回答,我想出了这个。简单得令人震惊。
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);
}
希望这是有意义的:(
- 将其非类型化为
IEnumerable<IEnumerable>
- 使用反射调用一个函数,该函数使用具有适当
T
的IEnumerable<IEnumerable<T>>
- 使用switch语句强制转换为适当的类型
- 使用
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>>();