C# Динамическое приведение списка

У меня есть устаревший код, в котором я пытаюсь сжать и удалить много повторяющихся повторов. У меня есть List<MessageItemViewModel> в качестве параметра метода, вызываемого из действия ASP.NET MVC POST. Фактические элементы списка будут подклассом MessageItemViewModel, например. MessageItemA1ViewModel. В вызываемом методе этот список приводился следующим образом:

switch (level1Code)
            {
                case "A1":
                    {
                        var list = messages.Cast<MessageItemA1ViewModel>().ToList();
                        file = printHelper.Print(list).Save(exportFormat);
                    }
                    break;
                case "A2":
                    {
                        var list = messages.Cast<MessageItemA2ViewModel>().ToList();
                        file = printHelper.Print(list).Save(exportFormat);
                    }
                    break;
                case "A3":

Всего около 80 случаев, но я хочу сократить это с помощью словаря:

private readonly Dictionary<string, Type> _messageItemMap = new 
    Dictionary<string, Type>
    {
        {"A1", typeof(MessageItemA1ViewModel)},
        {"A2", typeof(MessageItemA2ViewModel)},
        {"A3", typeof(MessageItemA3ViewModel)},

и динамически приводить элементы списка к фактическому базовому типу. Итак, я следил за примерами SO, и подход хорошо работает, объявляя статический метод и выполняя приведение к элементу в моем списке:

public static class Ext
{
    public static T Cast<T>(this object entity) where T : class
    {
        return entity as T;
    }
}


public class ExportHelper
{
    public byte[] GetFile(string level1Code, string title, List<MessageItemViewModel> messages, ExportFormat exportFormat)
    {
        .
        .
        .

        var castMethod = typeof(Ext).GetMethod("Cast", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(_messageItemMap[level1Code]);
        var anItemType = castMethod.Invoke(null, new object[] { messages[0] });

anItemType правильно соответствует фактическому типу, к которому я использую. В непосредственном окне при отладке вызов anItemType is MessageItemA2ViewModel возвращает true. НО мне нужно разыграть все предметы, поэтому я попробовал это:

var castMethod = typeof(Ext).GetMethod("Cast", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(_messageItemMap[level1Code]);
var theMessageItemModel = messages
    .Select(p => castMethod.Invoke(null, new object[] { p }))
    .Where(p => p != null)
    .ToList();

Первоначально казалось, что это сработало, поскольку при наведении курсора на theMessageItemModel отладка показывала значения как тип, к которому я привел, но это всего лишь магия инструментов Visual Studio. Проверка элементов в непосредственном окне, поскольку ранее элементы фактически определены как System.Object, а также я вижу, что типы возвращаемых вызовов Linq возвращают List<Object>. Это проблема позже, потому что theMessageItemModel передается другому универсальному методу библиотеки, где вызывается TypeDescriptor.GetProperties(typeof(T)), чтобы определить форму POCO для дальнейшей обработки. При передаче как List<Object> 0 свойств обнаруживается, но если я могу получить список, переданный как один из подклассов, например. List<MessageItemA1ViewModel>, тогда он вернет свойства.

Может ли кто-нибудь предложить, как мне использовать мой Cast RuntimeMethodInfo для создания правильного списка типов типа, соответствующего level1Code в моем словаре типов?

=== Обновление === На самом деле я не упомянул одну вещь, поскольку вызываемый мной библиотечный метод является универсальным, мне пришлось отразить и это, чтобы передать динамически приведенный тип в метод Print:

var printMethod = printHelper.GetType().GetMethod("Print").MakeGenericMethod(theMessageItemModel.GetType().GetGenericArguments().Single());
var docRender = printMethod.Invoke(printHelper, new object[] { theMessageItemModel }) as IDocumentRender;
file = docRender.Save(exportFormat);

Вызов theMessageItemModel.GetType().GetGenericArguments().Single() - это то место, где System.Object был найден и передан из-за повторного преобразования моего вызова Linq обратно в List<System.Object>.

Но во время выполнения у вас будет правильный тип. Вероятно, немедленное окно разрешает тип компиляции, потому что метод Invoke возвращает object

Yves Israel 10.08.2018 07:19

Во время выполнения произошел сбой, потому что вызов нашей библиотеки не работает, потому что при вызове TypeDescriptor.GetProperties (typeof (T)) T является System.Object, и у меня есть список нулевых свойств

Stephen York 10.08.2018 07:44

Но другая проблема: какой тип theMessageItemModel будет содержать? у вас не может быть списка с разнородным типом, если это не object, dynamic или интерфейс или базовый класс ...

Yves Israel 10.08.2018 08:17

Попробуйте явно указать тип theMessageItemModel как List<dynamic>

Yves Israel 10.08.2018 08:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
1 118
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Хорошо, поэтому у меня есть более глубокие размышления о заявлении LINQ. Поскольку во время компиляции невозможно узнать, что возвращается из моего castMethod, тогда LINQ может разрешить только объект, поэтому мне нужно, чтобы перечисляемый список действительно мог быть разрешен при запуске. Итак, я понял, что могу вставить LINQ в свой метод Cast:

public static IEnumerable<T> CastList<T>(this IEnumerable<object> entity) where T : class
{
    var ret = entity
        .Select(e => e as T)
        .ToList();

    return ret;
}

При вызове этого он возвращает, например, List<MessageItemA2ViewModel>, и теперь мой вызов theMessageItemModel.GetType().GetGenericArguments().Single() дает мне тип подкласса, который фактически находится в списке, поэтому метод Print вызывается правильно:

var castListMethod = typeof(Ext).GetMethod("CastList").MakeGenericMethod(_messageItemMap[level1Code]);
var theMessageItemModel = castListMethod.Invoke(null, new object[] { messages });

var printMethod = printHelper.GetType().GetMethod("Print").MakeGenericMethod(theMessageItemModel.GetType().GetGenericArguments().Single());
var docRender = printMethod.Invoke(printHelper, new [] { theMessageItemModel }) as IDocumentRender;
file = docRender.Save(exportFormat);

и 12 свойств обнаружены с помощью TypeDescriptor.GetProperties(typeof(T)), и в самом конце я получаю ожидаемый результат.

Другие вопросы по теме