Добавьте элементы к любому объекту ICollection <T>, используя отражение - PullRequest
0 голосов
/ 07 февраля 2019

Я пытаюсь поддерживать отображение в / из любого типа коллекции, которая реализует 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 голосов
/ 07 февраля 2019

Практически во всех коллекциях .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();
    }
}   

Попробуйте онлайн

Очевидно, вы могли бы оптимизировать это, переместив заполнение списка другим методом, а затем, возможно, использовать существующий метод настолько далеко, насколько это возможно, а затем использовать его там, где это невозможно.

0 голосов
/ 07 февраля 2019

Эта функциональность реализуется во время компиляции с использованием метода 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

0 голосов
/ 07 февраля 2019

При явной реализации интерфейса у объекта есть все методы интерфейса, но у типа объекта этого нет.

Итак, вот как добавить элемент в 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 });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...