C# - Return 泛型类型为 IEnumerable
C# - Return generic type as IEnumerable
我正在尝试使用类型字典解析泛型类型 T
到实例。
当 T
是 IEnumerable<>
时,我得到一个 LINQ Select
查询,其中包含该字典中的所有实例。但是,当我尝试 return 该查询时,我无法将其转换回 T
。我收到以下异常:
Additional information: Unable to cast object of type 'WhereSelectListIterator`2[System.Func`2[DiContainer.IServicesContainer,System.Object],System.Object]' to type 'System.Collections.Generic.IEnumerable`1[Tester.IService]'.
代码:
public T Resolve<T>()
{
Type typeToResolve = typeof(T);
if (m_TypeToConcrete.ContainsKey(typeToResolve))
{
return (T)m_TypeToConcrete[typeToResolve].GetSingle();
}
if (DetermineIfExactlyIEnumerable(typeToResolve))
{
Type underlyingType = typeToResolve.GetGenericArguments().First();
if (m_TypeToConcrete.ContainsKey(underlyingType))
{
// Throws invalid cast exception
return (T)m_TypeToConcrete[underlyingType].GetEnumerable();
}
}
}
public class FactoryMethodsForType
{
private List<Func<IServicesContainer, object>> m_FactoryMethods;
private IServicesContainer m_Container;
public FactoryMethodsForType(IServicesContainer container)
{
m_Container = container;
m_FactoryMethods = new List<Func<IServicesContainer, object>>();
}
public void AddFactoryMethod(Func<IServicesContainer, object> method)
{
m_FactoryMethods.Add(method);
}
public object GetSingle()
{
return m_FactoryMethods.Last().Invoke(m_Container);
}
public IEnumerable<object> GetEnumerable()
{
// Lazy
return m_FactoryMethods.Select(m => m.Invoke(m_Container));
}
}
我用这个技巧解决了类似的问题。
public static class GenericCast
{
//This is the only way to create a real thread safe dictionary might not be required in you case you can make convertors during init of just not care for multi thread. The normal dictionary will work fine it might only call the compile a few times.
public static ConcurrentDictionary<Type, Lazy<Func<IEnumerable<object>, object>>> creators = new ConcurrentDictionary<Type, Lazy<Func<IEnumerable<object>, object>>>();
public static T CastAs<T>(this IEnumerable<object> data)
{
var dataType = typeof(T).GenericTypeArguments.First();
var creator = creators.GetOrAdd(dataType, new Lazy<Func<IEnumerable<object>, object>>(() => {
var source = Expression.Parameter(
typeof(IEnumerable<object>));
var call = Expression.Call(
typeof(Enumerable), "Cast", new Type[] { dataType }, source);
var cast = Expression.Convert(call, typeof(object));
var exp = Expression.Lambda<Func<IEnumerable<object>, object>>(cast, source);
return exp.Compile();
}));
return (T)creator.Value(data);
}
}
此方法在第一次调用时会产生成本,但每隔一次调用对性能的影响很小。您可以像这样在代码中使用它
m_TypeToConcrete[underlyingType].GetEnumerable().CastAs<T>();
受 Filip 解决方案的启发,我使用了一个使用反射的解决方案,尽管目前性能不佳,但将 IEnumerable<object>
转换为 IEnumerable<myType>
并将其转换为 T
:
Type underlyingType = typeToResolve.GetGenericArguments().First();
if (m_TypeToConcrete.ContainsKey(underlyingType))
{
MethodInfo castMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.Cast));
IEnumerable<object> instances = m_TypeToConcrete[underlyingType].GetEnumerable();
return (T)castMethod.MakeGenericMethod(underlyingType).Invoke(instances, new object[] { instances });
}
我正在尝试使用类型字典解析泛型类型 T
到实例。
当 T
是 IEnumerable<>
时,我得到一个 LINQ Select
查询,其中包含该字典中的所有实例。但是,当我尝试 return 该查询时,我无法将其转换回 T
。我收到以下异常:
Additional information: Unable to cast object of type 'WhereSelectListIterator`2[System.Func`2[DiContainer.IServicesContainer,System.Object],System.Object]' to type 'System.Collections.Generic.IEnumerable`1[Tester.IService]'.
代码:
public T Resolve<T>()
{
Type typeToResolve = typeof(T);
if (m_TypeToConcrete.ContainsKey(typeToResolve))
{
return (T)m_TypeToConcrete[typeToResolve].GetSingle();
}
if (DetermineIfExactlyIEnumerable(typeToResolve))
{
Type underlyingType = typeToResolve.GetGenericArguments().First();
if (m_TypeToConcrete.ContainsKey(underlyingType))
{
// Throws invalid cast exception
return (T)m_TypeToConcrete[underlyingType].GetEnumerable();
}
}
}
public class FactoryMethodsForType
{
private List<Func<IServicesContainer, object>> m_FactoryMethods;
private IServicesContainer m_Container;
public FactoryMethodsForType(IServicesContainer container)
{
m_Container = container;
m_FactoryMethods = new List<Func<IServicesContainer, object>>();
}
public void AddFactoryMethod(Func<IServicesContainer, object> method)
{
m_FactoryMethods.Add(method);
}
public object GetSingle()
{
return m_FactoryMethods.Last().Invoke(m_Container);
}
public IEnumerable<object> GetEnumerable()
{
// Lazy
return m_FactoryMethods.Select(m => m.Invoke(m_Container));
}
}
我用这个技巧解决了类似的问题。
public static class GenericCast
{
//This is the only way to create a real thread safe dictionary might not be required in you case you can make convertors during init of just not care for multi thread. The normal dictionary will work fine it might only call the compile a few times.
public static ConcurrentDictionary<Type, Lazy<Func<IEnumerable<object>, object>>> creators = new ConcurrentDictionary<Type, Lazy<Func<IEnumerable<object>, object>>>();
public static T CastAs<T>(this IEnumerable<object> data)
{
var dataType = typeof(T).GenericTypeArguments.First();
var creator = creators.GetOrAdd(dataType, new Lazy<Func<IEnumerable<object>, object>>(() => {
var source = Expression.Parameter(
typeof(IEnumerable<object>));
var call = Expression.Call(
typeof(Enumerable), "Cast", new Type[] { dataType }, source);
var cast = Expression.Convert(call, typeof(object));
var exp = Expression.Lambda<Func<IEnumerable<object>, object>>(cast, source);
return exp.Compile();
}));
return (T)creator.Value(data);
}
}
此方法在第一次调用时会产生成本,但每隔一次调用对性能的影响很小。您可以像这样在代码中使用它
m_TypeToConcrete[underlyingType].GetEnumerable().CastAs<T>();
受 Filip 解决方案的启发,我使用了一个使用反射的解决方案,尽管目前性能不佳,但将 IEnumerable<object>
转换为 IEnumerable<myType>
并将其转换为 T
:
Type underlyingType = typeToResolve.GetGenericArguments().First();
if (m_TypeToConcrete.ContainsKey(underlyingType))
{
MethodInfo castMethod = typeof(Enumerable).GetMethod(nameof(Enumerable.Cast));
IEnumerable<object> instances = m_TypeToConcrete[underlyingType].GetEnumerable();
return (T)castMethod.MakeGenericMethod(underlyingType).Invoke(instances, new object[] { instances });
}