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);  

person ctrlShiftBryan    schedule 04.03.2010    source источник
comment
вы не показываете, как определяется p, опечатка?   -  person Hogan    schedule 04.03.2010
comment
По крайней мере, вы должны использовать оператор is. Я обновил ваш вопрос, чтобы правильно проверить тип без использования отражения.   -  person David Pfeffer    schedule 04.03.2010
comment
@David Pfeffer, оператор is применяется неправильно. is в этом контексте никогда не вернет true [propType всегда будет иметь тип Type]. вы хотите использовать propType == typeof(DateTime)   -  person johnny g    schedule 04.03.2010
comment
У меня Тип. Я оставил код, в котором я создаю экземпляр p. Но давайте просто предположим, что он у меня есть.   -  person ctrlShiftBryan    schedule 04.03.2010
comment
@johnny g, О, ты прав, я совершенно неправильно прочитал код. Я отменю свое изменение.   -  person David Pfeffer    schedule 04.03.2010
comment
хе-хе, не беспокойтесь, я заметил это, потому что сам делал это оооочень много раз ;) я бы тоже изменил его, чтобы использовать typeof, но я потратил свою репутацию на вопрос о вознаграждении :P   -  person johnny g    schedule 04.03.2010


Ответы (6)


TypeDescriptor на помощь!:

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

Все примитивные типы (плюс Nullable<TPrimitive> и множество других встроенных типов) уже интегрированы в инфраструктуру TypeConverter и, таким образом, поддерживаются «из коробки».

Чтобы интегрировать пользовательский тип в инфраструктуру TypeConverter, реализуйте собственный TypeConverter< /a> и используйте TypeConverterAttribute, чтобы украсить класс преобразоваться, с вашим новым TypeConverter

person Anton Gogolev    schedule 04.03.2010
comment
Как бы вы украсили DateTime этим атрибутом или он оформлен по умолчанию? - person Lazarus; 04.03.2010
comment
Поскольку вам нужно украсить класс для преобразования, разве это не сработает для его примера, где он пытается сделать это для встроенных типов? - person Davy8; 04.03.2010
comment
Вау здорово. не знал о TypeDescriptor. можно поподробнее об использовании? может быть, проведем нас через конкретное использование с типом значения фреймворка и сложным пользовательским типом? Я бы очень хотел увидеть, сколько работы требуется, чтобы интегрироваться в - person johnny g; 04.03.2010
comment
Спасибо, я только что удалил много строк, потому что я случайно реализовал большой класс Converter, чтобы делать именно то, что вы сделали здесь. - person iurisilvio; 02.10.2012
comment
Хороший! Очень хорошо знать. Для ясности можно добавить, что он работает для простых типов фреймворков из коробки. (См. комментарий Лазаря). - person Marc; 27.07.2015

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

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

РЕДАКТИРОВАТЬ: на самом деле в вашем случае вы не можете использовать дженерики (по крайней мере, нелегко). Вместо этого вы можете сделать это:

object value = Convert.ChangeType(myString, propType);
person Thomas Levesque    schedule 04.03.2010
comment
Как бы вы перешли от Type propType к propType.Parse(myString)? Мне любопытно посмотреть, как это работает. - person Lazarus; 04.03.2010
comment
Большое спасибо! Я искал что-то похожее, и ‹‹object value = Convert.ChangeType(myString, propType);›› сделал свое дело. - person Pietr Podschinski; 11.06.2020

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

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);
person Randall Borck    schedule 27.07.2012

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

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) если вам нужно что-то более обобщенное для базовых типов, вы можете попробовать что-то вроде Thomas Levesque предлагает - хотя, по правде говоря, я сам этого не пробовал, я незнаком с [последними?] расширениями до 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, внедрение этой службы выполняется за вас. Кроме того, поскольку он основан на экземпляре, у вас может быть несколько экземпляров, каждый со своим собственным набором правил преобразования — если, например, у вас есть несколько пользовательских элементов управления, каждый из которых генерирует свой собственный DateTime или другой формат сложной строки. .

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

person johnny g    schedule 04.03.2010

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

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

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

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

person Community    schedule 04.03.2010
comment
технически говоря, он не пытается вывести тип объекта из строки. ему дан тип объекта, и он хотел бы перейти от одного представления [строка] к другому [четко определенный экземпляр объекта] - person johnny g; 04.03.2010

Похоже, что то, что вы хотите сделать (по крайней мере, если задействованные типы являются типами, для которых вы не можете изменить исходный код), потребует утиный ввод, которого нет в C#

Если вам нужно делать это часто, я бы обернул логику в класс или метод, которому вы можете передать «myString» и «propType», и он вернет значение. В этом методе вы просто выполняете цепочку if, указанную выше, и возвращаете значение, когда оно находит подходящее. Вам все еще придется вручную перечислять все возможные типы, но вам нужно будет сделать это только один раз.

person Davy8    schedule 04.03.2010