Допустим, у нас есть следующий код (сильно упрощенная модификация моего собственного кода):
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();, но через отражение. И здесь у меня две проблемы:
typeof(Enumerable).GetMethod("ToList", new[] { typeof(IEnumerable) }); не находит метод IEnumerable.ToList(). Я знаю, что существует только одна версия этого метода и я могу просто использовать typeof(Enumerable).GetMethod("ToList"); — но все же, как его найти, явно указав тип параметров?ITable<TestData>? С моим кодом я получаю исключение:System.ArgumentException: 'Невозможно привести объект типа «ReflectionTest.Table`1[ReflectionTest.TestData]» для ввода System.Collections.Generic.IEnumerable`1[ReflectionTest.Table`1[ReflectionTest.TestData]]
@Heinzi Мой настоящий код намного сложнее, и мне обязательно нужно использовать отражение.
Похоже на linq2db. Можете ли вы подробно объяснить, что ожидается? Все можно сделать проще.
@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. Спасибо за прекрасную библиотеку.
Ну, это выполнимо, но что вы планируете делать с порядком вставки таблиц? Или это не имеет значения?
@SvyatlavDanyliv Порядок вставки таблиц в данном конкретном случае не имеет значения. Но скорее всего это будет важно в нашем следующем проекте. Там мы планируем жестко запрограммировать Dictionary<TableName, InsertOrder> и применить .OrderBy() к коду получения tableTypes, указанному выше. Видите ли вы какую-либо оптимизацию в этих двух случаях?
Здесь нет ничего особенного, просто сортируйте таблицы/типы.
Обратите внимание, что мы предпочитаем здесь технический стиль письма. Мы мягко не поощряем приветствия, «надеюсь, что вы сможете помочь», «спасибо», «заранее спасибо», благодарственные письма, приветы, добрые пожелания, подписи, «пожалуйста, можете ли вы помочь», материалы для болтовни и сокращенный txtspk, просьбы, как долго вы застрял, советы по голосованию, мета-комментарии и т. д. Просто объясните свою проблему и покажите, что вы пробовали, чего ожидали и что на самом деле произошло.





Для решения первой проблемы вы можете использовать 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 Ну тогда тебе придется сделать что-то вроде этого, я думаю.
Я тоже нашла похожий ответ. Спасибо, это некрасиво, но работает.
Для 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.
Спасибо. Я буду использовать эти удобные вспомогательные методы.
Вполне возможно, что то, что я говорю, не имеет отношения к вашему реальному варианту использования, потому что пример слишком упрощен, но в целом вам не нужно получать
ToListпутем размышления: дженерики — это в основном инструмент времени компиляции. Нет (обычно применяются исключения) нет необходимости использовать дженерики в динамически типизированном коде.