Можно ли подставить тип в обобщённый метод, если тип заранее не известен (.NET 4.8)?
Допустим есть обобщённый метод, который в зависимости от типа значения делает преобразование в строку:
string ValueToString<T>(object value)
{
if (typeof(T) == typeof(decimal))
return $"{value:N2}";
else if (typeof(T) == typeof(DateTime))
return $"{value:dd-MM-yyyy}";
else if (typeof(T) == typeof(string))
return $"'{value}'";
return null;
}
Использование метода:
public Main()
{
//например, есть таблица, содержащая колонки с разными типами данных
DataTable t=new DataTable();
t.Columns.Add("NO", typeof(decimal));
t.Columns.Add("DATE", typeof(DateTime));
t.Columns.Add("COMMENTS", typeof(string));
t.Rows.Add(10.5, DateTime.Now, "value 1");
t.Rows.Add(20.7, DateTime.Now, "value 2");
t.Rows.Add(30.9, DateTime.Now, "value 3");
//когда я хочу преобразовать значения ячеек в строку с помощью обобщающего метода начинаются проблемы
var str=t.AsEnumerable().SelectMany(r =>
t.Columns.OfType<DataColumn>().Select(c =>
{
//ПРИХОДИТСЯ ДЕЛАТЬ ТАК, НО ЕСЛИ ТИПОВ ЗНАЧИТЕЛЬНО БОЛЬШЕ, ТО КОД РАЗРАСТАЕТСЯ
if (c.DataType == typeof(decimal))
return ValueToString<decimal>(r[c]);
else if (c.DataType == typeof(DateTime))
return ValueToString<DateTime>(r[c]);
else if (c.DataType == typeof(string))
return ValueToString<string>(r[c]);
else
return "";
//НЕ РАБОТАЕТ!
return ValueToString<c.DataType>(r[c]);
}).ToArray()
);
}
Как сделать, чтобы можно было сразу подставить тип данных столбца в обобщающий метод, чтобы не приходилось делать ветвление для каждого типа?
Ответы (1 шт):
Код можно переписать, не используя параметр типа <T>. В итоге метод перестаёт быть обобщённым.
string ValueToString(object value)
{
if (value.GetType() == typeof(decimal))
return $"{value:N2}";
else if (value.GetType() == typeof(DateTime))
return $"{value:dd-MM-yyyy}";
else if (value.GetType() == typeof(string))
return $"'{value}'";
return null;
}
В более новых версиях языка и фреймворка доступен паттерн-матчинг.
С его помощью код метода можно переписать проще и лаконичнее:
string? ValueToString(object value)
{
if (value is decimal)
return $"{value:N2}";
else if (value is DateTime)
return $"{value:dd-MM-yyyy}";
else if (value is string)
return $"'{value}'";
return null;
}
Или ещё лучше:
string? ValueToString(object value)
{
return value switch
{
decimal => $"{value:N2}",
DateTime => $"{value:dd-MM-yyyy}",
string => $"'{value}'",
_ => null
};
}
Совсем хорошо (используем Expression body):
string? ValueToString(object value) =>
value switch
{
decimal => $"{value:N2}",
DateTime => $"{value:dd-MM-yyyy}",
string => $"'{value}'",
_ => null
};
Смотрите документацию: Patterns.
Возвращаемый тип меняем на string? - nullable.
Теперь обобщение в методе ValueToString<T> не используется.
В итоге код linq-выражения упрощается кардинально:
var str = t.AsEnumerable().SelectMany(r =>
t.Columns.OfType<DataColumn>().Select(c => ValueToString(r[c])));
Console.WriteLine(string.Join(", ", str));
Тип совсем не нужно указывать.
Это выражение тоже можно упростить:
var str = t.AsEnumerable().SelectMany(row =>
row.ItemArray.Select(x => ValueToString(x)));
И ещё лучше:
var str = t.AsEnumerable().SelectMany(row =>
row.ItemArray.Select(ValueToString));