Преобразование IList в общий c TResult, который может быть IEnumerable или object - PullRequest
1 голос
/ 29 января 2020

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

У меня есть метод десериализации, с которым я бы хотел использовать дженерики. Он должен быть способен принимать объекты или IEnumerable, например List<Person>, Person[], et c. Deserialize<TResult> работает .. вроде ... но я натолкнулся на блок, пытаясь понять, как на самом деле вернуть мой результат как TResult.

Вот что у меня есть:

    public static TResult Deserialize<TResult>(StreamReader inputStream)
    {
        if (inputStream.EndOfStream) return default(TResult);

        if (typeof(TResult).IsEnumerable())
        {
            Type itemType = typeof(TResult).GetItemType();
            IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(itemType));

            MethodInfo deserializeMethod = 
                typeof(SimpleFixedWidthSerializer)
                    .GetMethod("Deserialize", new[] { typeof(StreamReader) })
                    .MakeGenericMethod(new[] { itemType });

            object item = null;
            do
            {
                item = deserializeMethod.Invoke(null, new[] { inputStream });
                if (item != null)
                    list.Add(item);
            } while (item != null);

            list.Dump();
            return (TResult)list;
        }

        ...
    }

Глядя на Dump() результата, я вижу, что он был правильно десериализован в System.Collection.Generic.List типа Person, и каждый Person был индивидуально десерилизован должным образом ... но я не могу Кажется, выяснить, как добраться от IList до моего TResult. Пример вызова метода:

string testInput = "...";
using (MemoryStream mStream = new MemoryStream(Encoding.UTF8.GetBytes(testInput)))
using (StreamReader sr = new StreamReader(mStream))
    SimpleFixedWidthSerializer.Deserialize<Person[]>(sr).Dump();

Что приводит к

Unable to cast object of type 'System.Collections.Generic.List 1[UserQuery+Person]' to type 'Person[]'

Кто-нибудь знает, как я могу конвертировать мои IList правильно к моему TResult?

Ответы [ 4 ]

0 голосов
/ 30 января 2020

ОК, здесь идет попытка номер два. Идея в том, что вы не хотите усложнять ваш код без причины, поэтому я упростил создание List. Затем вы добавляете все свои объекты к нему и делаете все касты, прежде чем вернуться в конце. Для этого требуется второй обобщенный параметр c, который я назвал TItemType:

public static TResult Deserialize<TResult, TItemType>() where TResult : class
{
    if (inputStream.EndOfStream) return default(TResult);

    if (typeof(TResult).IsEnumerable())
    {
        var list = new List<object>();

        MethodInfo deserializeMethod = 
            typeof(SimpleFixedWidthSerializer)
                .GetMethod("Deserialize", new[] { typeof(StreamReader) })
                .MakeGenericMethod(new[] { itemType });

        object item = null;
        do
        {
            item = deserializeMethod.Invoke(null, new[] { inputStream });
            if (item != null)
                list.Add(item);
        } while (item != null);

        return typeof(TResult).IsArray
            ? list.Cast<TItemType>().ToArray() as TResult
            : list.Cast<TItemType>().ToList() as TResult;
    }
    ...
}
0 голосов
/ 29 января 2020

Когда вы вызываете Deserialize<Person[]>, TResult является типом массива. Поскольку объект list является List и List не являются массивами, это приведение: (TResult)list завершится неудачно. Вместо этого вызовите Deserialize<List<Person>> или Deserialize<IList<Person>>.

. Источник проблемы состоит в том, что когда TResult равно IsEnumerable, вы возвращаете List во всех случаях. Но все Enumerable не List (например, массив).

0 голосов
/ 29 января 2020

Последний путь, который я прошел, выглядит следующим образом:

        public static TResult Deserialize<TResult>(BufferedStreamReader inputStream) where TResult : class
        {
            if (inputStream.EndOfStream) return default;

            if (typeof(TResult).IsEnumerable()) //arrays, lists, etc are a special cases
            {
                Type itemType = typeof(TResult).GetItemType();

                MethodInfo methodInfo = typeof(FixedWidthSerializer)
                    .GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
                    .FirstOrDefault(m => m.Name == "DeserializeEnumerable")
                    .MakeGenericMethod(new[] { typeof(TResult), itemType });

                return methodInfo.Invoke(null, new[] { inputStream }) as TResult;
            }
            ...
         }
        private static TResult DeserializeEnumerable<TResult, TType>(BufferedStreamReader inputStream) //ignore the IDE, this is being called through reflection
            where TResult : class
            where TType : class
        {
            var list = new List<TType>();

            TType item;
            while ((item = Deserialize<TType>(inputStream)) != null)
                list.Add(item);

            if (typeof(TResult).IsArray)
                return list.ToArray<TType>() as TResult;
            return list.AsEnumerable<TType>() as TResult;
        }
0 голосов
/ 29 января 2020

Измените свой метод так, чтобы он:

  • Объявляет list как объект типа List<> (т.е. вы можете использовать ключевое слово var). Это необходимо, потому что IList не имеет метода ToArray().
  • Ограничивает тип TResult типами классов (это необходимо для того, чтобы вызов ToArray() работал позже.
  • Преобразуйте ваш список в массив, вызвав ToArray() перед возвратом позже.
  • Выполните приведение с использованием ключевого слова as, чтобы оно разрешалось во время выполнения, а не перехватывалось компилятором.

Эти изменения изменят ваш метод на:

public static TResult Deserialize<TResult>(StreamReader inputStream) where TResult : class
{
    if (inputStream.EndOfStream) return default(TResult);

    if (typeof(TResult).IsEnumerable())
    {
        Type itemType = typeof(TResult).GetItemType();
        var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(itemType));

        MethodInfo deserializeMethod = 
            typeof(SimpleFixedWidthSerializer)
                .GetMethod("Deserialize", new[] { typeof(StreamReader) })
                .MakeGenericMethod(new[] { itemType });

        object item = null;
        do
        {
            item = deserializeMethod.Invoke(null, new[] { inputStream });
            if (item != null)
                list.Add(item);
        } while (item != null);

        list.Dump();
        return list.ToArray() as TResult;
    }

    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...