2010-03-04 6 views
39

У меня есть тип, строка и объект.C#: Динамический анализ из System.Type

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

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

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

Вы не показываете, как определяется 'p', опечатка? – Hogan

+1

По крайней мере, вы должны использовать оператор 'is'. Я обновил ваш вопрос, чтобы правильно проверить тип, не используя рефлексию. –

+3

@David Pfeffer, оператор 'is' неправильно применен. 'is' в этом контексте никогда не вернет true [' propType' всегда будет иметь тип 'Type']. вы хотите использовать 'propType == typeof (DateTime)' –

ответ

65

TypeDescriptor спасать:

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

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

+2

Как бы вы украсили DateTime с этот атрибут или он оформлен по умолчанию? – Lazarus

+0

Поскольку вам нужно украсить класс, который нужно преобразовать, разве это не сработает для его примера, где он пытается сделать это для встроенных типов? – Davy8

+0

Вау, круто. не знал о 'TypeDescriptor'. не могли бы вы подробно остановиться на использовании? возможно, возьмем нас за конкретное использование с использованием типа значения структуры и сложного пользовательского типа? я очень хотел бы посмотреть, как много работы связано с интеграцией в –

13

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

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

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

object value = Convert.ChangeType(myString, propType); 
+0

Как вы относитесь к типу propType к propType.Parse (myString)? Мне любопытно посмотреть, как это работает. – Lazarus

+0

Я только что обновил свой ответ –

0

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

Таким образом, для любого общего подхода, вы будете нуждаться по крайней мере:

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

Посмотрите на статический метод Convert.ChangeType().

+3

технически, он не пытается * вывести * тип объекта из строки. ему задан тип объекта и он хотел бы перейти от одного представления [строки] к другому [четко определенному экземпляру объекта] –

-1

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

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

2

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

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 Levesquesuggests - хотя, по правде говоря, я этого не делал сам, я не знаком с [последними?] расширениями до 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 или другой сложный строковый формат.

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

6

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

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

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

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

p.SetValue(instance, value, null); 
Смежные вопросы