我正在尝试支持通过反射实现ICollection<T>
的任何种类的集合之间的映射,因为ICollection<T>
需要实现Add方法。
这对于大多数常见的集合类型都适用,但是对于诸如LinkedList<T>
这样的边缘情况却无法解决,其中Add方法是隐藏的,只能通过将LinkedList<T>
强制转换为ICollection<T>
来调用。
但是由于它不是协变的,因此无法转换为ICollection<>
。
我正在考虑的另一个选择是搜索Add的隐式和显式实现,但是当接口是通用的时,我看不到有关如何执行此操作的任何信息?
采取的正确方法是什么?
已更新,以显示从xml到对象映射的代码段。
private object CollectionXmlNodeListToObject(
XmlNodeList nodeList, System.Type collectionType)
{
// this is not possible because ICollection<> is not covariant
object collection = Convert.ChangeType(
CreateInstanceOfType(collectionType), ICollection<>);
Type containedType = collectionType.GetTypeInfo().GenericTypeArguments[0];
foreach (XmlNode node in nodeList)
{
object value = CreateInstanceOfType(containedType);
if (containedType.IsClass && MetaDataCache.Contains(containedType))
value = ToObject(value, node, node.Name);
else
value = node.InnerText;
// this throws NullReferenceException when the type is LinkedList,
// because this is explicitly implemented in LinkedList
collectionType.GetMethod("Add")
.Invoke(collection, new[] { value });
}
return collection;
}
我正在编写一个小框架,使用类和属性属性将对象映射到xml。所以我不能使用泛型,因为所有这些都是在运行时完成的。
我最初是在检查IEnumerable
之前,但是遇到了其他麻烦(字符串实现IEnumberable
并且是不可变的),因此我认为最安全的做法是坚持使用ICollection<>
>
答案 0 :(得分:2)
通过显式接口实现,对象具有所有接口方法,但对象的Type没有。
因此,这是通过反射将项目添加到LinkedList<T>
或任何ICollection<T>
的方法:
var ll = new LinkedList<int>();
var t = typeof(int);
var colType = typeof(ICollection<>).MakeGenericType(t);
var addMethod = colType.GetMethod("Add");
addMethod.Invoke(ll, new object[] { 1 });
答案 1 :(得分:1)
使用Cast<T>()
方法可在编译时满足此功能。您只需要一个运行时版本,该版本非常简单:
static public object LateCast(this ICollection items, Type itemType)
{
var methodDefintionForCast = typeof(System.Linq.Enumerable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(mi => mi.Name == "Cast")
.Select(mi => mi.GetGenericMethodDefinition())
.Single(gmd => gmd != null && gmd.GetGenericArguments().Length == 1);
var method = methodDefintionForCast.MakeGenericMethod(new Type[] { itemType });
return method.Invoke(null, new[] { items });
}
现在,您可以获取任何非通用集合,并在运行时使其通用。例如,这两个是等效的:
var list = nodeList.Cast<XmlNode>();
object list = nodeList.LateCast(typeof(XmlNode));
您可以使用以下方法转换整个集合:
static public IEnumerable ConvertToGeneric(this ICollection source, Type collectionType)
{
return source.LateCast(collectionType.GetGenericArguments()[0]) as IEnumerable;
}
object list = nodeList.ConvertToGeneric(nodeList, typeof(ICollection<XmlNode>));
此解决方案适用于链接列表以及所有其他集合类型。
在DotNetFiddle上查看我的工作示例
答案 2 :(得分:0)
几乎所有.NET集合都使用IEnumerable<T>
作为构造函数,因此您可以使用它:
private static object CollectionXmlNodeListToObject(System.Type collectionType)
{
// T
Type containedType = collectionType.GetTypeInfo().GenericTypeArguments[0];
// List<T>
Type interimListType = typeof(List<>).MakeGenericType(containedType);
// IEnumerable<T>
Type ienumerableType = typeof(IEnumerable<>).MakeGenericType(containedType);
IList interimList = Activator.CreateInstance(interimListType) as IList;
interimList.Add(null);
interimList.Add(null);
interimList.Add(null);
interimList.Add(null);
// If we can directly assign the interim list, do so
if (collectionType == interimListType || collectionType.IsAssignableFrom(interimListType))
{
return interimList;
}
// Try to get the IEnumerable<T> constructor and use that to construct the collection object
var constructor = collectionType.GetConstructor(new Type[] { ienumerableType });
if (constructor != null)
{
return constructor.Invoke(new object[] { interimList });
}
else
{
throw new NotImplementedException();
}
}
显然,您可以通过将列表填充移动到另一种方法来优化此方法,然后尽可能使用现有方法,然后在无法使用的地方使用此方法。