Как определить, что объект является универсальной коллекцией и какие типы он содержит? - PullRequest
9 голосов
/ 16 апреля 2009

У меня есть утилита сериализации строк, которая берет переменную (почти) любого типа и преобразует ее в строку. Так, например, согласно моему соглашению, целочисленное значение 123 будет сериализовано как «i: 3: 123» (i = целое число; 3 = длина строки; 123 = значение).

Утилита обрабатывает все примитивные типы, а также некоторые неуниверсальные коллекции, такие как ArrayLists и Hashtables. Интерфейс имеет вид

public static string StringSerialize(object o) {}

и внутренне я определяю тип объекта и сериализую его соответственно.

Теперь я хочу обновить мою утилиту для обработки общих коллекций. Самое смешное, что я не могу найти подходящую функцию для определения того, что объект является универсальной коллекцией и какие типы он содержит - какие именно фрагменты информации мне нужны для правильной сериализации. На сегодняшний день я использую кодирование вида

if (o is int) {// do something}

но, похоже, это не работает с генериками.

Что вы рекомендуете?


РЕДАКТИРОВАТЬ: Благодаря Lucero , я стал ближе к ответу, но я застрял в этой небольшой синтаксической головоломке здесь:

if (t.IsGenericType) {
  if (typeof(List<>) == t.GetGenericTypeDefinition()) {
    Type lt = t.GetGenericArguments()[0];
    List<lt> x = (List<lt>)o;
    stringifyList(x);
  }
}

Этот код не компилируется, потому что "lt" не допускается в качестве аргумента <T> объекта List<>. Почему бы и нет? И какой правильный синтаксис?

Ответы [ 3 ]

7 голосов
/ 16 апреля 2009

Используйте тип для сбора необходимой информации.

Для универсальных объектов вызовите GetType (), чтобы получить их тип, а затем проверьте IsGenericType, чтобы выяснить, является ли он универсальным. Если это так, вы можете получить определение общего типа, которое можно сравнить, например, так: typeof(List<>)==yourType.GetGenericTypeDefinition(). Чтобы узнать, что такое универсальные типы, используйте метод GetGenericArguments, который будет возвращать массив используемых типов.

Для сравнения типов вы можете сделать следующее: if (typeof(int).IsAssignableFrom(yourGenericTypeArgument)).


РЕДАКТИРОВАТЬ, чтобы ответить на продолжение:

Просто заставьте ваш метод stringifyList принять IEnumerable (не универсальный) в качестве параметра и, возможно, также известный аргумент универсального типа, и все будет в порядке; затем вы можете использовать foreach, чтобы просмотреть все элементы и обработать их в зависимости от аргумента типа, если это необходимо.

5 голосов
/ 16 апреля 2009

Re вашей головоломки; Я предполагаю, stringifyList это общий метод? Вам нужно вызвать его с отражением:

MethodInfo method = typeof(SomeType).GetMethod("stringifyList")
            .MakeGenericMethod(lt).Invoke({target}, new object[] {o});

, где {target} - null для статического метода или this для метода экземпляра в текущем экземпляре.

Далее - я бы не предположил, что все коллекции a: основаны на List<T>, b: универсальных типах. Важная вещь: они реализуют IList<T> для некоторых T?

Вот полный пример:

using System;
using System.Collections.Generic;
static class Program {
    static Type GetListType(Type type) {
        foreach (Type intType in type.GetInterfaces()) {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(IList<>)) {
                return intType.GetGenericArguments()[0];
            }
        }
        return null;
    }
    static void Main() {
        object o = new List<int> { 1, 2, 3, 4, 5 };
        Type t = o.GetType();
        Type lt = GetListType(t);
        if (lt != null) {
            typeof(Program).GetMethod("StringifyList")
                .MakeGenericMethod(lt).Invoke(null,
                new object[] { o });
        }
    }
    public static void StringifyList<T>(IList<T> list) {
        Console.WriteLine("Working with " + typeof(T).Name);
    }
}
1 голос
/ 16 апреля 2009

На самом базовом уровне все универсальные списки реализуют IEnumerable<T>, который сам по себе является потомком IEnumerable. Если вы хотите сериализовать список, вы можете просто преобразовать его в IEnumerable и перечислить в нем общие объекты.

Причина, по которой вы не можете сделать

Type lt = t.GetGenericArguments()[0];
List<lt> x = (List<lt>)o;
stringifyList(x);

потому, что дженерики по-прежнему должны быть статически строго типизированы, а вы пытаетесь создать динамический тип. List<string> и List<int>, несмотря на использование одного и того же универсального интерфейса, являются двумя совершенно разными типами, и вы не можете приводить между ними.

List<int> intList = new List<int>();
List<string> strList = intList; // error!

Какой тип получит stringifyList(x)? Самый простой интерфейс, который вы можете здесь передать, это IEnumerable, поскольку IList<T> не наследуется от IList.

Чтобы сериализовать общий список, вам необходимо сохранить информацию об исходном типе списка, чтобы его можно было заново создать с помощью Activator. Если вы хотите немного оптимизировать систему, чтобы вам не приходилось проверять тип каждого члена списка в вашем методе stringify, вы можете напрямую передать тип, извлеченный из списка.

...