Вызов универсального метода расширения с типом, известным во время выполнения, в качестве аргумента

У меня есть общее расширение:

public static T Get<T>(this DataRow row, string columnName)
{
    if (row == null || string.IsNullOrWhiteSpace(columnName) || row.IsNull(columnName))
        if (typeof(T) == typeof(string))
            return (T)(object)string.Empty;
        else
            return default(T);
    else
    {
        if (row[columnName].GetType() == typeof(T))
            return row.Field<T>(columnName);
        else
        {
            return (T)Convert.ChangeType(row[columnName], typeof(T));
        }
    }
}

Я хочу использовать его с известным типом времени выполнения:

obj = new K();
var props = obj.GetType().GetProperties();
for (int i = 0; i < props.Length; i++)
{
    if (row.Table.Columns.Contains(props[i].Name))
    {
        // props[i].SetValue(obj, row[prop]);                         
        props[i].SetValue(obj, row.Get<props[i].PropertyType>(props[i].Name));
    }
}

Но я получаю:

Ошибка компилятора CS0019: оператор «оператор» нельзя применить к операндам типа «тип» и «тип».

Как я могу вызвать общее расширение, если тип известен во время выполнения?

Я пробовал это решение:

Обобщенные шаблоны в C#, использующие тип переменной в качестве параметра

но безуспешно - может быть, потому, что я использую расширение?

Дженерики - это скорее вещь времени компиляции. Как бы вы программировали возвращаемый тип, если бы он был известен только во время выполнения? Общий здесь вам не поможет.

Ralf 01.05.2024 13:29

Я не вижу смысла в этом коде. Это ничем не отличается от row[columnName] as T. Если вам не нужны дженерики, не используйте методы LINQ.

Panagiotis Kanavos 01.05.2024 13:47

Если вы хотите создать конкретный объект из DataTable, вы можете использовать LINQ Select. Хотя лучшим вариантом было бы использовать, например, Dapper для загрузки результатов непосредственно в виде объектов вместо копирования результатов в DataTable.

Panagiotis Kanavos 01.05.2024 13:49

Если целью является сопоставление результатов запроса с объектами по имени, вы можете заменить весь этот код, используя AutoMapper.Data. Вы также можете использовать механизмы настройки Automapper для создания более сложных сопоставлений.

Panagiotis Kanavos 01.05.2024 13:57

Мой метод Get<> помогает мне в таких преобразованиях, как: short => int, null => "" и т. д. И теперь я хочу использовать его в методе сопоставления (DB => объект), как в Dapper. Но, читая все ответы, которые я вижу, я сильно усложнил задачу :)

b0bik 01.05.2024 14:03
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
55
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я нашел решение:

var mi = typeof(Extensions).GetMethod("Get", [typeof(DataRow), typeof(string)]);
var generic = mi.MakeGenericMethod(props[i].PropertyType);
props[i].SetValue(obj, generic.Invoke(null, [row, props[i].Name ]));

Расширения — это статический класс, в котором я объявил метод Get расширения.

Теперь вы использовали дженерик, не используя то, что дженерики дают вам (типобезопасность). Вы просто усложнили задачу, чем просто использовали для этого обычное решение во время выполнения. Посмотрите на свой код и подумайте, почему подпись Convert.ChangeType такая, какая она есть (не универсальная).

Ralf 01.05.2024 13:40

Этот код ничем не отличается от row[columnName] as T. Кажется, существует большая путаница в отношении того, что является частью ADO.NET, неуниверсальной библиотеки, что такое расширения, такие как LINQ-to-DataSet, и что необходимо для создания конкретных объектов из результатов запроса.

Panagiotis Kanavos 01.05.2024 13:51

Какова цель Get<T> вернуть сильный тип, когда SetValue нужен только object?

Olivier Jacot-Descombes 01.05.2024 13:58
Ответ принят как подходящий

Ответ таков: вы не можете разрешить универсальный тип во время выполнения. Обобщения всегда разрешаются во время компиляции. Они не динамичны!

Но нет смысла использовать строго типизированный объект во время выполнения, поскольку метод PropertyInfo.SetValue в любом случае требует параметра object. В динамическом сценарии вы можете просто работать со своим методом Get, возвращая object, и использовать его как есть при вызове SetValue.

Цель дженериков — обеспечить безопасность типов, но это может произойти только во время компиляции, поскольку именно компилятор обеспечивает эту безопасность, проверяя, совместимы ли статические типы с присваиваниями и операциями, которые вы над ними выполняете. . Во время выполнения нет безопасности типов, поэтому нет смысла использовать дженерики.

измените свой метод на:

public static object Get(this DataRow row, string columnName, Type propertyType)
{
    if (row is null || String.IsNullOrWhiteSpace(columnName) || row.IsNull(columnName)) {
        if (propertyType == typeof(string)) {
            return string.Empty;
        } else if (propertyType.IsValueType) {
            return Activator.CreateInstance(propertyType);
        } else {
            return null;
        }
    } else {
        object value = row[columnName];
        if (value.GetType() == propertyType) {
            return value;
        } else {
            return Convert.ChangeType(value, propertyType);
        }
    }
}

и измените свой вызов на

PropertyInfo prop = props[i];
prop.SetValue(obj, row.Get(prop.Name, prop.PropertyType));

Я думаю, что перегруженная версия Get (с дополнительным параметром) — лучшее решение. Спасибо!

b0bik 01.05.2024 21:37

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