Как найти метод IEnumerable<T>.ToList(), явно указав тип параметров, а затем вызвать его с настраиваемым типом параметра?

Допустим, у нас есть следующий код (сильно упрощенная модификация моего собственного кода):

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace ReflectionTest
{
    public interface IDataContext
    { }

    sealed class DataContext : IDataContext
    { }

    public interface ITable<T> : IEnumerable<T>
        where T : class
    {
        List<T> source { get; }
    }


    sealed class Table<T> : ITable<T>
        where T : class, new()
    {
        public Table()
        {
            source = new List<T>() { new T() };
        }

        public List<T> source { get; set; }

        public IEnumerator<T> GetEnumerator() => source.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => source.GetEnumerator();
    }

    public static class DataExtensions
    {
        public static ITable<T> GetTable<T>(this IDataContext dataContext)
            where T : class, new()
        {
            return new Table<T>();
        }
    }

    public class TestData
    {
        public string Name = "Test";
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            var dataContext = new DataContext();

            //var ret = dataContext.GetTable<TestData>().ToList(); but via reflection:
            var getTableMethod = typeof(DataExtensions).GetMethod("GetTable", new[] { typeof(IDataContext) });
            var getTableGeneric = getTableMethod.MakeGenericMethod(typeof(TestData));
            var testDataITable = getTableGeneric.Invoke(null, new object[] { dataContext });

            //var toListMethod = typeof(Enumerable).GetMethod("ToList", new[] { typeof(IEnumerable) });   this doesn't find IEnumerable<T>.ToList() method!
            var toListMethod = typeof(Enumerable).GetMethod("ToList");
            var toListGeneriс = toListMethod.MakeGenericMethod(testDataITable.GetType());

            //System.ArgumentException: 'Unable to cast object of type "ReflectionTest.Table`1[ReflectionTest.TestData]"
            //to type "System.Collections.Generic.IEnumerable`1[ReflectionTest.Table`1[ReflectionTest.TestData]]".'
            var ret = toListGeneriс.Invoke(null, new object[] { testDataITable });

            Console.ReadKey();
        }
    }
}

Я пытаюсь вызвать var ret = dataContext.GetTable<TestData>().ToList();, но через отражение. И здесь у меня две проблемы:

  1. typeof(Enumerable).GetMethod("ToList", new[] { typeof(IEnumerable) }); не находит метод IEnumerable.ToList(). Я знаю, что существует только одна версия этого метода и я могу просто использовать typeof(Enumerable).GetMethod("ToList"); — но все же, как его найти, явно указав тип параметров?
  2. Как мне тогда правильно вызвать этот метод с типом параметра ITable<TestData>? С моим кодом я получаю исключение:

System.ArgumentException: 'Невозможно привести объект типа «ReflectionTest.Table`1[ReflectionTest.TestData]» для ввода System.Collections.Generic.IEnumerable`1[ReflectionTest.Table`1[ReflectionTest.TestData]]

Вполне возможно, что то, что я говорю, не имеет отношения к вашему реальному варианту использования, потому что пример слишком упрощен, но в целом вам не нужно получать ToList путем размышления: дженерики — это в основном инструмент времени компиляции. Нет (обычно применяются исключения) нет необходимости использовать дженерики в динамически типизированном коде.

Heinzi 18.05.2024 11:05

@Heinzi Мой настоящий код намного сложнее, и мне обязательно нужно использовать отражение.

bairog 19.05.2024 15:48

Похоже на linq2db. Можете ли вы подробно объяснить, что ожидается? Все можно сделать проще.

Svyatoslav Danyliv 19.05.2024 17:15

@SvyatlavDanyliv Ух ты, невероятная догадка! Это действительно для linq2db. У меня есть база данных Sql Ce (49 таблиц), и мне нужно скопировать все данные в ту же базу данных SQLite. И вместо того, чтобы звонить var existingData1= dataContextSqlCe.GetTable<MyType1>().ToList(); dataContextSQLite.BulkCopy(existingData1); 49 раз, я решил использовать отражение. Я получаю типы таблиц через var tableTypes = dataContextSqlCe.GetType().GetProperties().Where(p => typeof(ITable<>).IsSameOrParentOf(p.PropertyType)).Select(p => p.PropertyType.GetGenericArguments()[0]) и выполняю итерацию. P.S. Спасибо за прекрасную библиотеку.

bairog 19.05.2024 20:22

Ну, это выполнимо, но что вы планируете делать с порядком вставки таблиц? Или это не имеет значения?

Svyatoslav Danyliv 19.05.2024 20:29

@SvyatlavDanyliv Порядок вставки таблиц в данном конкретном случае не имеет значения. Но скорее всего это будет важно в нашем следующем проекте. Там мы планируем жестко запрограммировать Dictionary<TableName, InsertOrder> и применить .OrderBy() к коду получения tableTypes, указанному выше. Видите ли вы какую-либо оптимизацию в этих двух случаях?

bairog 20.05.2024 07:54

Здесь нет ничего особенного, просто сортируйте таблицы/типы.

Svyatoslav Danyliv 20.05.2024 09:43

Обратите внимание, что мы предпочитаем здесь технический стиль письма. Мы мягко не поощряем приветствия, «надеюсь, что вы сможете помочь», «спасибо», «заранее спасибо», благодарственные письма, приветы, добрые пожелания, подписи, «пожалуйста, можете ли вы помочь», материалы для болтовни и сокращенный txtspk, просьбы, как долго вы застрял, советы по голосованию, мета-комментарии и т. д. Просто объясните свою проблему и покажите, что вы пробовали, чего ожидали и что на самом деле произошло.

halfer 12.06.2024 21:23
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
91
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Для решения первой проблемы вы можете использовать Type.MakeGenericMethodParameter для создания «параметра типа».

Возвращает объект типа подписи, который можно передать в параметр массива Type[] метода GetMethod для представления ссылки на универсальный параметр.

Затем используйте это, чтобы создать Type, представляющее IEnumerable<TSource>. Передав это GetMethod, вы получите желаемый метод.

var typeVariable = Type.MakeGenericMethodParameter(0);
var parameterType = typeof(IEnumerable<>).MakeGenericType(typeVariable)
var toListMethod = typeof(Enumerable).GetMethod("ToList", [parameterType]);

Вторая проблема связана с тем, что вы неправильно параметризовали ToList. Напомним подпись ToList:

public static List<TSource> ToList<TSource> (
    this IEnumerable<TSource> source
);

TSource должен быть типом элемента коллекции, TestData, а не типом самой коллекции.

var toListGeneriс = toListMethod.MakeGenericMethod(typeof(TestData));

Спасибо. Но Type.MakeGenericMethodParameter доступен только в .Net Core 2.1 (или .Net Standard 2.1). Какой код мне следует использовать в .Net Framework 4.7.2 (или .Net Standard 2.0)?

bairog 19.05.2024 15:45

@bairog Ну тогда тебе придется сделать что-то вроде этого, я думаю.

Sweeper 19.05.2024 15:55

Я тоже нашла похожий ответ. Спасибо, это некрасиво, но работает.

bairog 19.05.2024 15:57

Для linq2db есть вспомогательные методы, помогающие работать с отражениями. Но если ваша цель — скопировать базу данных в другую, есть более простой вариант.

public static class BulkCopyExtensions
{
    public static void CopyData<T>(this IQueryable<T> sourceQuery, IDataContext destination) where T : class
    {
        destination.GetTable<T>().BulkCopy(sourceQuery.AsEnumerable());
    }

    public static void CopyData<T>(this IDataContext source, IDataContext destination) where T : class
    {
        CopyData(source.GetTable<T>(), destination);
    }

    // MemberHelper.MethodOfGeneric is linq2db method
    private static readonly MethodInfo _copyDataConnections = MemberHelper.MethodOfGeneric(() => ((DataConnection)null!).CopyData<object>(null!));

    public static void CopyAllData(this IDataContext source, IDataContext destination) 
    {
        var typesForCopy = source.GetType().GetProperties()
            .Where(p => typeof(ITable<>).IsSameOrParentOf(p.PropertyType))
            .Select(p => p.PropertyType.GetGenericArguments()[0])
            .ToList();

        foreach (var type in typesForCopy)
        {
            _copyDataConnections.MakeGenericMethod(type).Invoke(null, [source, destination]);
        }
    }
}

Итак, в вашем случае копирование базы данных — это просто вызов метода расширения CopyAllData.

dataContextSqlCe.CopyAllData(dataContextSqLite);

Обратите внимание, что этот метод не учитывает порядок вставок, и у вас часто возникают ошибки FK.

Спасибо. Я буду использовать эти удобные вспомогательные методы.

bairog 20.05.2024 07:59

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

Как преобразовать это выражение Linq в лямбда-выражение (представляем онлайн-конвертер)
Общее выражение для поиска частичных совпадений строк с динамическим списком полей в запросе LINQ
EF Core 8 подсчитывает отношения «многие ко многим», генерирующие неверный запрос
C# Linq объединение двух подтаблиц
Коллекция доступна только для чтения. Исключение с методом EfCore Include
Невозможно привести List<T> к классу, производному от List<T>
Как получить индекс повторяющихся элементов вместе с диапазоном пустых элементов после него. ЛИНК
Чтение вложенного XML из файла и использование классов для хранения информации
Чтение XML в формат LINQ для проверки данных файла журнала. Условия в формате списка. linq не получает преобразованный XML
Могу ли я настроить автоматическое выполнение асинхронной операции после завершения другой асинхронной операции?