Можно ли подставить тип в обобщённый метод, если тип заранее не известен (.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 шт):

Автор решения: Alexander Petrov

Код можно переписать, не используя параметр типа <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));
→ Ссылка