2010-03-04 15 views
39

Tengo un tipo, una cadena y un objeto.C#: análisis dinámico de System.Type

¿Hay alguna forma de que pueda llamar al método de análisis sintáctico o convertir ese tipo en la cadena de forma dinámica?

Básicamente cómo puedo eliminar las sentencias if en esta lógica

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

Y hago calle detrás de la misma familia.

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


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

No se muestra cómo se define 'p', typo? – Hogan

+1

Como mínimo, debe usar el operador 'is'. He actualizado tu pregunta para verificar correctamente el tipo sin usar el reflejo. –

+3

@David Pfeffer, el operador 'is' se aplica incorrectamente. 'is' en este contexto nunca volverá verdadero [' propType' siempre será de tipo 'Type']. Desea utilizar 'propType == typeof (DateTime)' –

Respuesta

65

TypeDescriptor al rescate:

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

de integrar en TypeConverter infraestructura, implementar su propio TypeConverter y decorar la clase para ser convertidos con ella con TypeConverterAttribute

+2

¿Cómo decorarías DateTime con este atributo o está decorado por defecto? – Lazarus

+0

Dado que necesita decorar la clase que se convertirá, ¿no sería así para su ejemplo en el que intenta hacerlo para los tipos incorporados? – Davy8

+0

wow, genial. no sabía sobre 'TypeDescriptor'. ¿Podrías dar más detalles sobre el uso? tal vez nos lleve a través de un uso concreto con un tipo de valor de marco y un tipo personalizado complejo? Me gustaría mucho ver cuánto trabajo se necesita para integrar en –

13

Esto debería funcionar para todos los tipos primitivos, y para los tipos que implementan IConvertible

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

EDITAR: en realidad, en su caso, no puede usar genéricos (al menos no fácil). En su lugar se puede hacer eso:

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

¿Cómo llegar desde Type propType a propType.Parse (myString)? Tengo curiosidad por ver cómo funciona esto. – Lazarus

+0

Acabo de actualizar mi respuesta –

0

Es técnicamente imposible mirar a una cadena, y saber con certeza qué tipo que representa.

Por lo tanto, para cualquier enfoque genérico, necesitará al menos:

  1. la cadena que se va a analizar
  2. del tipo utilizado para el análisis.

Tome una mirada en el método estático Convert.ChangeType().

+3

técnicamente hablando, él no está tratando de * inferir * tipo de objeto de una cadena. se le asigna el tipo de objeto y le gustaría pasar de una representación [cadena] a otra [instancia de objeto bien definida] –

-1

Parece que lo que quiere hacer (al menos si los tipos involucrados son los tipos a los que no se puede modificar la fuente) requeriría duck typing que no está en C#

Si necesita hacer esto mucho, envolvería la lógica en una clase o método que puede pasar "myString" y "propType" a y devolvería valor. En ese método, simplemente haría la cadena if que tiene arriba y devolverá el valor cuando encuentre una que coincida. Tendría que listar manualmente todos los tipos posibles, pero solo tendría que hacerlo una vez.

2

Depende de lo que desea lograr.

1) si simplemente está tratando de limpiar su código, y quitar la comprobación de tipos repetitivo, entonces lo que quiere hacer es centralizar sus cheques en un método, 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) si lo haría como algo más general para tipos básicos, puedes probar algo como Thomas Levesquesuggests - aunque, en verdad, no lo he intentado yo mismo, no estoy familiarizado con las extensiones [recientes?] de Convert. También una muy buena sugerencia.

3) de hecho, es probable que desee fusionar tanto el 1) como el 2) anteriores en una única extensión que le permita admitir la conversión de valores básicos y la compatibilidad con tipos complejos explícitos.

4) si quiere ser completamente "manos libres", entonces también podría usar de forma predeterminada la deserialización simple antigua [Xml o Binary, cualquiera/o]. Por supuesto, esto restringe su entrada, es decir, todas las entradas deben estar en un formato Xml o Binario apropiado. Honestamente, esto es probablemente excesivo, pero vale la pena mencionarlo.

Por supuesto, todos estos métodos hacen esencialmente lo mismo. No hay magia en ninguno de ellos, en algún momento alguien está realizando una búsqueda lineal [ya sea una búsqueda implícita a través de cláusulas si consecutivas o bajo el capó a través de las instalaciones de conversión y serialización de .Net].

5) si quiere mejorar el rendimiento, entonces lo que quiere hacer es mejorar la parte de "búsqueda" de su proceso de conversión. Cree una lista explícita de "tipos soportados", cada tipo corresponde a un índice en una matriz. En lugar de especificar el Tipo en una llamada, usted especifica el índice.

EDIT: Así, mientras que la búsqueda lineal es clara y rápida, también se me ocurre que sería aún más rápido si el consumidor simplemente obtuviera funciones de conversión y las invocara directamente. Es decir, el consumidor sabe de qué tipo le gustaría convertir a [este es un hecho], por lo que si se tiene que convertir muchos artículos al mismo tiempo,

// 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; 
    } 

} 

y sería utilizado como

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

Prefiero este último enfoque, porque le da completo control sobre la conversión. Si usa un contenedor de Inversión de Control [IoC] como Castle Windsor o Unity, entonces la inyección de este servicio se realiza para usted. Además, como está basado en instancia, puede tener varias instancias, cada una con su propio conjunto de reglas de conversión; por ejemplo, si tiene varios controles de usuario, cada uno generando su propio DateTime u otro formato de cadena complejo.

Diablos, incluso si usted quisiera soportar múltiples reglas de conversión para un solo tipo de destino, eso también es posible, simplemente tiene que extender los parámetros del método para especificar cuál.

6

me encontré con este problema y esto es cómo lo resolví:

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

... y funcionó para mí.

Para resumir, está tratando de encontrar un método estático de "Parse" en el tipo de objeto que toma solo una cadena como argumento. Si encuentra un método así, inícielo con el parámetro de cadena que está tratando de convertir. Como p es PropertyInfo para mi tipo, finalicé este método estableciendo mi instancia con el valor siguiente:

p.SetValue(instance, value, null);