56 votes

C# : Analyse dynamique à partir de System.Type

J'ai un Type, une Chaîne et un Objet.

Existe-t-il un moyen d'appeler la méthode d'analyse ou de conversion pour ce type de chaîne de façon dynamique ?

En gros, comment 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 faire 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);

1 votes

Vous ne montrez pas comment p est défini, une faute de frappe ?

1 votes

Au minimum, vous devriez utiliser l'option is opérateur. J'ai mis à jour votre question pour vérifier correctement le type sans utiliser la réflexion.

3 votes

@David Pfeffer, is est appliqué de manière incorrecte. is dans ce contexte ne retournera jamais vrai [ propType sera toujours de type Type ]. vous voulez utiliser propType == typeof(DateTime)

108voto

Anton Gogolev Points 59794

TypeDescriptor à la rescousse !

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

Tous les types primitifs (plus Nullable<TPrimitive> et de nombreux autres types intégrés) sont déjà intégrés dans l'infrastructure de TypeConverter, et sont donc pris en charge dès la sortie de la boîte.

Pour intégrer un type personnalisé dans le TypeConverter l'infrastructure, mettez en œuvre votre propre TypeConverter et utiliser TypeConverterAttribute pour décorer la classe à convertir, avec votre nouvelle TypeConverter

3 votes

Comment décorer DateTime avec cet attribut ou est-il décoré par défaut ?

0 votes

Puisque vous devez décorer la classe à convertir, cela ne fonctionnerait-il pas dans l'exemple où il essaie de le faire pour les types intégrés ?

0 votes

Wow, cool. je ne connaissais pas TypeDescriptor Pourriez-vous nous présenter une utilisation concrète avec un type de valeur de cadre et un type personnalisé complexe ? J'aimerais vraiment voir combien de travail cela implique pour l'intégrer.

17voto

Thomas Levesque Points 141081

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

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

EDIT : en fait, dans votre cas, vous ne pouvez pas utiliser les génériques (pas facilement du moins). A la place, vous pourriez faire ce :

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

0 votes

Comment passer de Type propType à propType.Parse(myString) ? Je suis curieux de voir comment cela fonctionne.

0 votes

Merci beaucoup ! Je cherchais quelque chose de similaire et <<valeur de l'objet = Convert.ChangeType(myString, propType);>> a fait l'affaire.

8voto

Randall Borck Points 217

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 ça a marché pour moi.

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

p.SetValue(instance, value, null);

2voto

johnny g Points 2472

Cela dépend de ce que vous souhaitez accomplir.

1) si vous essayez simplement de nettoyer votre code, et de supprimer les vérifications de type répétitives, alors ce que vous voulez faire, c'est 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 Levesque suggère - bien qu'en vérité, je n'aie pas tenté de le faire moi-même, je ne suis pas familier avec les extensions [récentes ? Convert . C'est aussi une très bonne suggestion.

3) en fait, vous souhaitez probablement fusionner les deux points 1) et 2) ci-dessus en une seule extension qui vous permettrait de prendre en charge la conversion des valeurs de base et le support explicite des types complexes.

4) si vous voulez être complètement "mains libres", vous pouvez aussi choisir par défaut la bonne vieille désérialisation [Xml ou binaire, au choix]. Bien sûr, cela contraint vos entrées - c'est-à-dire que toutes les entrées doivent être dans un format Xml ou Binaire approprié. Honnêtement, c'est probablement exagéré, mais cela vaut la peine d'être mentionné.

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

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

EDIT : Ainsi, bien que la recherche linéaire soit pratique et rapide, il me semble que ce serait encore plus rapide si le consommateur obtenait simplement les fonctions de conversion et les invoquait directement. En d'autres termes, le consommateur sait quel type il souhaite convertir [c'est une donnée], donc s'il a besoin de convertir de nombreux éléments à la fois,

// 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 d'inversion de contrôle [IoC] comme Castle Windsor ou Unity, l'injection de ce service est effectuée pour vous. De plus, comme il s'agit d'un instance vous pouvez avoir plusieurs instances, chacune avec son propre ensemble de règles de conversion - si, par exemple, vous avez plusieurs contrôles d'utilisateur, chacun générant sa propre règle de conversion DateTime ou tout autre format de chaîne complexe.

Même si vous souhaitez prendre en charge plusieurs règles de conversion pour un seul type de cible, c'est également possible. Il vous suffit d'étendre les paramètres de la méthode pour spécifier laquelle.

0voto

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

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

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

Jetez un coup d'œil à la statique Convert.ChangeType() método.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X