web-dev-qa-db-fra.com

C #: analyse dynamique à partir de System.Type

J'ai un type, une chaîne et un objet.

Est-il possible d'appeler la méthode d'analyse ou de convertir ce type de manière dynamique sur la chaîne?

Fondamentalement, comment puis-je supprimer les instructions if dans cette logique

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

Et fais quelque chose de plus comme ça.

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


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

TypeDescriptor to rescue:

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

Pour intégrer l’infrastructure TypeConverter, implémentez votre propre classe TypeConverter et décorez-la pour la convertir avec TypeConverterAttribute

75
Anton Gogolev

Cela devrait fonctionner pour tous les types primitifs et pour les types qui implémentent IConvertible

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

EDIT: dans votre cas, vous ne pouvez pas utiliser de génériques (pas facilement du moins). Au lieu de cela, vous pouvez le faire:

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

J'ai rencontré ce problème et voici comment je l'ai résolu:

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

... et cela a fonctionné pour moi.

Pour résumer, vous essayez de trouver une méthode statique "Parse" sur le type d'objet qui prend une seule chaîne en argument. Si vous trouvez une telle méthode, appelez-la avec le paramètre de chaîne que vous essayez de convertir. Puisque p est le PropertyInfo pour mon type, j'ai terminé cette méthode en définissant mon instance avec la valeur suivante:

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

Cela dépend de ce que vous souhaitez accomplir.

1) si vous essayez simplement de nettoyer votre code et de supprimer la vérification de type répétitive, vous voulez centraliser vos vérifications dans une méthode, 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 vous souhaitez quelque chose de plus généralisé pour les types de base, vous pouvez essayer quelque chose comme Thomas Levesquesuggère - bien que, en vérité, je n'ai pas essayé cela moi-même, je ne connais pas les extensions [récentes?] à Convert. Aussi une très bonne suggestion.

3) en fait, vous souhaiterez probablement fusionner les deux 1) et 2) ci-dessus en une seule extension qui vous permettrait de prendre en charge la conversion de valeurs de base et la prise en charge de types complexes explicites.

4) si vous voulez être complètement "mains libres", vous pouvez également utiliser par défaut la plaine ancienne désérialisation [XML ou binaire, soit/ou]. Bien sûr, cela limite votre entrée - c’est-à-dire que toutes les entrées doivent être au format XML ou binaire approprié. Honnêtement, c'est probablement exagéré, mais il convient de le mentionner.

Bien sûr, toutes ces méthodes font essentiellement la même chose. Il n'y a pas de magie dans aucun d'entre eux, à un moment donné quelqu'un effectue une recherche linéaire [qu'il s'agisse d'une recherche implicite au travers de clauses if séquentielles ou sous le capot via des fonctions de conversion et de sérialisation .Net].

5) si vous souhaitez améliorer les performances, vous souhaitez améliorer la partie "recherche" de votre processus de conversion. Créez une liste explicite de "types pris en charge", chaque type correspondant à un index dans un tableau. Au lieu de spécifier le type lors d'un appel, vous spécifiez ensuite l'index.

EDIT: Ainsi, bien que la recherche linéaire soit nette et rapide, il me semble également que ce serait encore plus rapide si le consommateur obtenait simplement des fonctions de conversion et les invoquait directement. En d’autres termes, le consommateur sait à quel type il aimerait se convertir [c’est une donnée], donc s’il doit convertir plusieurs éléments en même temps, 

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

}

et il serait utilisé comme

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

Je préfère de loin cette dernière approche, car elle vous donne complet le contrôle de la conversion. Si vous utilisez un conteneur Inversion of Control [IoC] comme Castle Windsor ou Unity, l'injection de ce service est effectuée pour vous. De plus, comme il est basé sur instance, vous pouvez avoir plusieurs instances, chacune avec son propre ensemble de règles de conversion. Si, par exemple, vous avez plusieurs contrôles utilisateur, chacun générant son propre format DateTime ou un autre format de chaîne complexe.

Heck, même si vous vouliez prendre en charge plusieurs règles de conversion pour un seul type de cible, cela est également possible, vous devez simplement étendre les paramètres de méthode pour spécifier lequel.

2
johnny g

Il est techniquement impossible de regarder une chaîne et de savoir avec certitude quel type elle représente.

Donc, pour toute approche générique, vous aurez besoin d'au moins:

  1. la chaîne à analyser
  2. le type utilisé pour l'analyse.

Examinez la méthode statique Convert.ChangeType().

0
user286353