Зависит от того, что вы хотели бы достичь.
1) если вы просто пытаетесь очистить свой код и удалить повторяющиеся проверки типов, то вам нужно централизовать проверки в методе comme
public static T To<T> (this string stringValue)
{
T value = default (T);
if (typeof (T) == typeof (DateTime))
{
// insert custom or convention System.DateTime
// deserialization here ...
}
// ... add other explicit support here
else
{
throw new NotSupportedException (
string.Format (
"Cannot convert type [{0}] with value [{1}] to type [{2}]." +
" [{2}] is not supported.",
stringValue.GetType (),
stringValue,
typeof (T)));
}
return value;
}
2) если вы хотите что-то более обобщенное для базовых типов, вы можете попробовать что-то вроде Томас Левеск предлагает - хотя на самом деле я не пытался это делать сам, я не знаком с [недавними?] расширениями Convert
. Также очень хорошее предложение.
3) на самом деле, вы, вероятно, захотите объединить оба вышеприведенных пункта 1) и 2) в одно расширение, которое позволит вам поддерживать базовое преобразование значений и явную поддержку сложных типов.
4) если вы хотите быть полностью свободным от рук, то вы также можете по умолчанию использовать обычную старую десериализацию [Xml или Binary, либо / или]. Конечно, это ограничивает ваш ввод - т. Е. Весь ввод должен быть в формате Xml или Binary. Честно говоря, это, вероятно, излишне, но стоит упомянуть.
Конечно, все эти методы по сути делают одно и то же. Ни в одном из них нет магии, в какой-то момент кто-то выполняет линейный поиск [будь то неявный поиск с помощью последовательных выражений if или изнутри с помощью средств .Net преобразования и сериализации].
5) если вы хотите повысить производительность, то вам нужно улучшить часть поиска в процессе конвертации. Создайте явный список «поддерживаемых типов», каждый тип которого соответствует индексу в массиве. Вместо того, чтобы указывать тип для вызова, вы затем указываете индекс.
РЕДАКТИРОВАТЬ: поэтому, хотя линейный поиск является аккуратным и быстрым, мне также кажется, что было бы еще быстрее, если бы потребитель просто получал функции преобразования и вызывал их напрямую. То есть потребитель знает, какой тип он хочет преобразовать в [это данность], поэтому, если ему нужно преобразовать много элементов одновременно,
// S == source type
// T == target type
public interface IConvert<S>
{
// consumers\infrastructure may now add support
int AddConversion<T> (Func<S, T> conversion);
// gets conversion method for local consumption
Func<S, T> GetConversion<T> ();
// easy to use, linear look up for one-off conversions
T To<T> (S value);
}
public class Convert<S> : IConvert<S>
{
private class ConversionRule
{
public Type SupportedType { get; set; }
public Func<S, object> Conversion { get; set; }
}
private readonly List<ConversionRule> _map = new List<ConversionRule> ();
private readonly object _syncRoot = new object ();
public void AddConversion<T> (Func<S, T> conversion)
{
lock (_syncRoot)
{
if (_map.Any (c => c.SupportedType.Equals (typeof (T))))
{
throw new ArgumentException (
string.Format (
"Conversion from [{0}] to [{1}] already exists. " +
"Cannot add new conversion.",
typeof (S),
typeof (T)));
}
ConversionRule conversionRule = new ConversionRule
{
SupportedType = typeof(T),
Conversion = (s) => conversion (s),
};
_map.Add (conversionRule);
}
}
public Func<S, T> GetConversion<T> ()
{
Func<S, T> conversionMethod = null;
lock (_syncRoot)
{
ConversionRule conversion = _map.
SingleOrDefault (c => c.SupportedType.Equals (typeof (T)));
if (conversion == null)
{
throw new NotSupportedException (
string.Format (
"Conversion from [{0}] to [{1}] is not supported. " +
"Cannot get conversion.",
typeof (S),
typeof (T)));
}
conversionMethod =
(value) => ConvertWrap<T> (conversion.Conversion, value);
}
return conversionMethod;
}
public T To<T> (S value)
{
Func<S, T> conversion = GetConversion<T> ();
T typedValue = conversion (value);
return typedValue;
}
// private methods
private T ConvertWrap<T> (Func<S, object> conversion, S value)
{
object untypedValue = null;
try
{
untypedValue = conversion (value);
}
catch (Exception exception)
{
throw new ArgumentException (
string.Format (
"Unexpected exception encountered during conversion. " +
"Cannot convert [{0}] [{1}] to [{2}].",
typeof (S),
value,
typeof (T)),
exception);
}
if (!(untypedValue is T))
{
throw new InvalidCastException (
string.Format (
"Converted [{0}] [{1}] to [{2}] [{3}], " +
"not of expected type [{4}]. Conversion failed.",
typeof (S),
value,
untypedValue.GetType (),
untypedValue,
typeof (T)));
}
T typedValue = (T)(untypedValue);
return typedValue;
}
}
и будет использоваться как
// as part of application innitialization
IConvert<string> stringConverter = container.Resolve<IConvert<string>> ();
stringConverter.AddConversion<int> (s => Convert.ToInt32 (s));
stringConverter.AddConversion<Color> (s => CustomColorParser (s));
...
// a consumer elsewhere in code, say a Command acting on
// string input fields of a form
//
// NOTE: stringConverter could be injected as part of DI
// framework, or obtained directly from IoC container as above
int someCount = stringConverter.To<int> (someCountString);
Func<string, Color> ToColor = stringConverter.GetConversion <Color> ();
IEnumerable<Color> colors = colorStrings.Select (s => ToColor (s));
Я предпочитаю этот последний подход, потому что он дает вам полный контроль над преобразованием. Если вы используете контейнер Inversion of Control [IoC], такой как Castle Windsor или Unity, то внедрение этой услуги для вас. Кроме того, поскольку он основан на экземпляр , вы можете иметь несколько экземпляров, каждый со своим набором правил преобразования - если, например, у вас есть несколько пользовательских элементов управления, каждый из которых генерирует свою собственную DateTime
или другую сложную строку формат.
Черт, даже если вы хотите поддерживать несколько правил преобразования для одного целевого типа, это также возможно, вам просто нужно расширить параметры метода, чтобы указать, какой из них.