it-swarm.com.ru

C #: динамический анализ из System.Type

У меня есть Тип, Строка и Объект.

Есть ли способ, которым я могу вызвать метод синтаксического анализа или преобразовать для этого типа в строке динамически?

В основном, как мне удалить операторы if в этой логике

object value = new object();    
String myString = "something";
Type propType = p.PropertyType;

if(propType == Type.GetType("DateTime"))
{
    value = DateTime.Parse(myString);
}

if (propType == Type.GetType("int"))
{
    value = int.Parse(myString);
}

И сделать что-нибудь еще, как это.

object value = new object();
String myString = "something";
Type propType = p.PropertyType;


//this doesn't actually work
value = propType .Parse(myString);  
45
ctrlShiftBryan

TypeDescriptor для спасения:

var converter = TypeDescriptor.GetConverter(propType);
var result = converter.ConvertFrom(myString);

Чтобы интегрироваться в инфраструктуру TypeConverter, внедрите свой собственный TypeConverter и украсьте класс, который будет преобразован в него, с помощью TypeConverterAttribute

75
Anton Gogolev

Это должно работать для всех примитивных типов и для типов, которые реализуют IConvertible

public static T ConvertTo<T>(object value)
{
    return (T)Convert.ChangeType(value, typeof(T));
}

Правка: на самом деле в вашем случае вы не можете использовать дженерики (по крайней мере, не легко). Вместо этого вы можете сделать это:

object value = Convert.ChangeType(myString, propType);
14
Thomas Levesque

Я столкнулся с этой проблемой, и вот как я решил ее:

value = myString;
var parse = propType.GetMethod("Parse", new[] { typeof(string) });
if (parse != null) {
  value = parse.Invoke(null, new object[] { value });
}

... и это сработало для меня.

Подводя итог, вы пытаетесь найти статический метод "Parse" для типа объекта, который принимает только одну строку в качестве аргумента. Если вы найдете такой метод, вызовите его с параметром строки, который вы пытаетесь преобразовать. Поскольку p является PropertyInfo для моего типа, я завершил этот метод, установив свой экземпляр со значением следующим образом:

p.SetValue(instance, value, null);
7
Randall Borck

Зависит от того, что вы хотели бы достичь.

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) если вы хотите улучшить производительность, то вы хотите улучшить «поисковую» часть процесса конверсии. Создайте явный список «поддерживаемых типов», каждый тип которого соответствует индексу в массиве. Вместо того, чтобы указывать тип для вызова, вы затем указываете индекс.

EDIT: так что, хотя линейный поиск является аккуратным и быстрым, мне также кажется, что было бы еще быстрее, если бы потребитель просто получал функции преобразования и вызывал их напрямую. То есть потребитель знает, какой тип он хочет преобразовать в [это является данным], поэтому, если ему нужно преобразовать много элементов одновременно, 

// 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, то внедрение этой услуги для вас. Кроме того, поскольку он основан на instance, у вас может быть несколько экземпляров, каждый со своим набором правил преобразования - если, например, у вас есть несколько пользовательских элементов управления, каждый из которых генерирует свой собственный DateTime или другой сложный формат строки.

Черт возьми, даже если вы хотите поддерживать несколько правил преобразования для одного целевого типа, это также возможно, вам просто нужно расширить параметры метода, чтобы указать, какой именно.

2
johnny g

Технически невозможно посмотреть на строку и точно знать, какой тип она представляет.

Итак, для любого общего подхода вам понадобится как минимум:

  1. строка для анализа
  2. тип, используемый для разбора.

Взгляните на статический метод Convert.ChangeType().

0
user286353