У меня есть общее расширение:
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#, использующие тип переменной в качестве параметра
но безуспешно - может быть, потому, что я использую расширение?
Я не вижу смысла в этом коде. Это ничем не отличается от row[columnName] as T
. Если вам не нужны дженерики, не используйте методы LINQ.
Если вы хотите создать конкретный объект из DataTable, вы можете использовать LINQ Select
. Хотя лучшим вариантом было бы использовать, например, Dapper для загрузки результатов непосредственно в виде объектов вместо копирования результатов в DataTable.
Если целью является сопоставление результатов запроса с объектами по имени, вы можете заменить весь этот код, используя AutoMapper.Data. Вы также можете использовать механизмы настройки Automapper для создания более сложных сопоставлений.
Мой метод Get<> помогает мне в таких преобразованиях, как: short => int, null => "" и т. д. И теперь я хочу использовать его в методе сопоставления (DB => объект), как в Dapper. Но, читая все ответы, которые я вижу, я сильно усложнил задачу :)
Я нашел решение:
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
такая, какая она есть (не универсальная).
Этот код ничем не отличается от row[columnName] as T
. Кажется, существует большая путаница в отношении того, что является частью ADO.NET, неуниверсальной библиотеки, что такое расширения, такие как LINQ-to-DataSet, и что необходимо для создания конкретных объектов из результатов запроса.
Какова цель Get<T>
вернуть сильный тип, когда SetValue
нужен только object
?
Ответ таков: вы не можете разрешить универсальный тип во время выполнения. Обобщения всегда разрешаются во время компиляции. Они не динамичны!
Но нет смысла использовать строго типизированный объект во время выполнения, поскольку метод 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 (с дополнительным параметром) — лучшее решение. Спасибо!
Дженерики - это скорее вещь времени компиляции. Как бы вы программировали возвращаемый тип, если бы он был известен только во время выполнения? Общий здесь вам не поможет.