使用反射将项目添加到ICollection <t>的任何对象中

时间:2019-02-07 02:27:23

标签: c# .net reflection

我正在尝试支持通过反射实现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<>

3 个答案:

答案 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();
    }
}   

Try it online

显然,您可以通过将列表填充移动到另一种方法来优化此方法,然后尽可能使用现有方法,然后在无法使用的地方使用此方法。