У меня есть система, которая ретранслирует стороннее ПО, в котором хранятся элементы данных.
Элементы данных в хранилище имеют свойства DataType и Value для описания подчеркнутого объекта данных. Каждый DataElement можно получить по имени из хранилища. (как внешняя база данных)
public class DataElemenet
{
public string Type { get; }
public string Value { get; }
}
Я хочу создать объект BL, чтобы абстрагироваться от особенностей DataElement следующим образом:
public class DataGetter
{
private readonly DataElement dataObject;
public DataGetter(string name)
{
this.dataObject = <third_party>.GetObject(name);
}
public int GetValue()
{
if (this.dataObject.Type != "integer")
throw new Exception();
return int.Parse(this.dataObject.Value);
}
}
Теперь в стороннем ПО Type может быть разных типов (например, List<int>, double, List<string>....)
Поскольку я не могу перегрузить GetValue, чтобы вернуть другой тип возвращаемого значения, я попробовал общий подход.
public T GetValue<T>()
{
if (typeof(T) != this.dataObject.Type)
throw new Exception();
return (T)Convert.ChangeType(this.dataObject.Value, typeof(T));
}
Хотя этот подход будет работать, я хочу оптимизировать его следующим образом (обрабатывать все типы данных в каждом конкретном случае): РЕДАКТИРОВАТЬ
Как упоминалось в одном из комментариев, этот подход работает только для примитивов и не может поддерживать преобразование, например, в List<int>. Поэтому я попытался использовать следующий подход:
public T GetValue<T>()
{
if (typeof(T) != this.dataObject.Type)
throw new Exception();
if (typeof(T) == typeof(int))
return int.Parse(this.dataObject.Value);
else if (typeof(T) == typeof(bool))
return bool.Parse(this.dataObject.Value);
....
}
Компилятор жалуется, что не может преобразовать int / bool (в приведенных выше случаях) в T.
Можно ли это сделать?
Весь смысл универсального метода в том, что вы делаете одно и то же для каждого типа. Если вам нужно использовать условные операторы для проверки типа и делать что-то другое для каждого из них, вам не следует использовать дженерики в первую очередь.
Как вы думаете, почему это будет оптимизация по сравнению с Convert.ChangeType?
Вы говорите, что этот первый общий метод будет работать, но так ли это на самом деле? Вы тестировали его со всеми теми различными типами, которые вы упомянули? В документации для Convert.ChangeType говорится следующее: «Для успешного преобразования значение должно реализовывать интерфейс IConvertible, потому что метод просто переносит вызов в соответствующий метод IConvertible. Метод требует поддержки преобразования значения в convertType». Как можно преобразовать string в List<int> в таких условиях?
Обратите внимание, что вызывающая сторона должна указать T в этом случае (не может быть выведена), поэтому вызывающей стороне придется делать GetValue<int> и даже такие уродливые вещи, как GetValue<List<string>>. Еще хуже то, что он может предоставлять неподдерживаемый тип. Все это заставляет меня думать, что лучше просто использовать методы GetInt, GetBool и т. д.
@jmcilhinney: действительно, это была еще одна причина, по которой я решил разделить метод на каждый случай. я согласен, я не упомянул об этом в самом вопросе
Затем вы также можете добавить object Get(), который преобразует Value в тип Type и возвращает как объект. В основном этот дизайн будет таким же, как SqlDataReader должен читать столбцы строки, предоставленной базой данных (у нее есть GetInt32 GetBoolean и т. д. и т. д.): learn.microsoft.com/en-us/dotnet/api/…
Да, это возможно: сначала вам нужно выполнить приведение к object (в этом случае помните о боксе/распаковке).
public T GetValue<T>()
{
if (typeof(T) != this.dataObject.Type)
throw new Exception();
if (typeof(T) == typeof(int))
return (T)(object)int.Parse(this.dataObject.Value);
else if (typeof(T) == typeof(bool))
return (T)(object)bool.Parse(this.dataObject.Value);
....
}
ДЕМО
Вы можете положиться на новый интерфейс IParsable<T> из .NET7, который имеет общий статический метод Parse, который вы можете использовать в общих контекстах.
Сохраняя исходную попытку, это может выглядеть примерно так:
public T GetValue<T>()
where T : IParsable<T>
{
if (typeof(T) != this.dataObject.Type)
throw new Exception();
return T.Parse(this.dataObject.Value, CultureInfo.InvariantCulture);
}
Обратите внимание, что тип, который вы сейчас передаете, должен реализовывать статический абстрактный метод Parse из IParsable, что делает подавляющее большинство стандартных примитивных и базовых типов. Если вы затем захотите поддерживать такие вещи, как произвольные списки из строки, вы можете создать свои собственные пользовательские коллекции, которые реализуют IParsable, и поместить туда преобразование из строки в список.
Преимущество этого подхода в том, что он действительно полиморфен: он не заставляет вас проверять все возможные типы в гигантском операторе switch. Таким образом, он значительно более ремонтопригоден и расширяем.
Я не понимаю проблемы, если у вас есть решение. Запрашивать оптимизацию — это просить мнения и не по теме.