当我在运行时只知道IEnumerable<IEnumerable<T>>
时,我需要创建一个T
。
我已经建立了我的收藏品:
new List<List<object>>()
其中内部列表中的所有对象都是T
然而,由于共同/逆转(永远不会记住它是什么!)List
List
的{{1}}不是IEnumerable
的{{1}}。
我该怎么办?
我已尝试使用IEnumerable
,但它认为Convert.ChangeType
不是List
线索:阅读问题。再次。我说我在运行时只知道IConvertible
。
答案 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)
IEnumerable<IEnumerable>
IEnumerable<IEnumerable<T>>
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
}
<小时/> 实施例
{{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>>();