web-dev-qa-db-fra.com

Type de retour générique variable en C #

Existe-t-il un moyen pour une méthode de renvoyer l'un des types génériques d'une méthode? Par exemple, j'ai les éléments suivants:

public static T ParseAttributeValue<T>(this XElement element, string attribute)
    {
        if(typeof(T) == typeof(Int32))
        {
            return Int32.Parse(element.Attribute(attribute).Value);
        }

        if(typeof(T) == typeof(Double))
        {
            return Double.Parse(element.Attribute(attribute).Value);
        }

        if(typeof(T) == typeof(String))
        {
            return element.Attribute(attribute).Value;
        }

        if(typeof(T) == typeof(ItemLookupType))
        {
            return Enum.Parse(typeof(T), element.Attribute(attribute).Value);
        }
    }

(Ceci n'est qu'une maquette très rapide, je suis conscient que tout code de production aurait besoin d'être beaucoup plus complet dans les contrôles nuls, etc.)

Mais le compilateur ne l'aime pas, se plaignant que Int32 ne puisse pas être converti implicitement en T (cela ne fonctionne pas non plus avec une distribution). Je peux comprendre cela. Au moment de la compilation, il n’a aucun moyen de savoir ce que T est, mais je le vérifie au préalable. Y at-il de toute façon je peux faire ce travail?

20
richzilla

J'ai déjà utilisé ces types de méthodes génériques. Le moyen le plus simple d'obtenir l'inférence de type est de fournir une fonction de convertisseur générique.

public static T ParseAttributeValue<T>
          (this XElement element, string attribute, Func<string, T> converter)
{
  string value = element.Attribute(attribute).Value;
  if (String.IsNullOrWhiteSpace(value)) {
    return default(T);
  }

  return converter(value);
}

Vous pouvez l'utiliser comme suit:

int index = element.ParseAttributeValue("index", Convert.ToInt32);
double price = element.ParseAttributeValue("price", Convert.ToDouble);

Vous pouvez même fournir vos propres fonctions et avoir tout le plaisir du monde (même renvoyer des types anonymes):

ItemLookupType lookupType = element.ParseAttributeValue("lookupType",
  value => Enum.Parse(typeof(ItemLookupType), value));

var item = element.ParseAttributeValue("items",
  value => {
    List<string> items = new List<string>();
    items.AddRange(value.Split(new [] { ',' }));
    return items;
  });
21
Joshua

.Net a déjà un tas d'excellentes routines de conversion de chaînes que vous pouvez utiliser! Un TypeConverter peut faire le gros du travail lourd pour vous. Dans ce cas, vous n'avez pas à vous soucier de fournir vos propres implémentations d'analyse pour les types intégrés.

Notez qu'il existe des versions des API tenant compte des paramètres régionaux sur TypeConverter qui peuvent être utilisées si vous devez gérer des valeurs d'analyse exprimées dans différentes cultures.

Le code suivant analysera les valeurs en utilisant la culture par défaut:

using System.ComponentModel;

public static T ParseAttributeValue<T>(this XElement element, string attribute)
{
    var converter = TypeDescriptor.GetConverter(typeof(T));
    if (converter.CanConvertFrom(typeof(string)))
    {
        string value = element.Attribute(attribute).Value;
        return (T)converter.ConvertFromString(value);
    }

    return default(T);
}

Cela fonctionnera pour beaucoup de types intégrés, et vous pouvez décorer les types personnalisés avec une variable TypeConverterAttribute pour leur permettre de participer également au jeu de conversion de type. Cela signifie qu'à l'avenir, vous pourrez analyser de nouveaux types sans avoir à modifier l'implémentation de ParseAttributeValue.

voir: http://msdn.Microsoft.com/en-us/library/system.componentmodel.typeconverter.aspx

8
Monroe Thomas

Pourquoi utilisez-vous le paramètre type comme type de retour? Cela fonctionnerait, nécessite juste un casting après avoir appelé:

public static Object ParseAttributeValue<T>(this XElement element, string attribute)
{
    if(typeof(T) == typeof(Int32))
    {
        return Int32.Parse(element.Attribute(attribute).Value);
    }

    if(typeof(T) == typeof(Double))
    {
        return Double.Parse(element.Attribute(attribute).Value);
    }

    if(typeof(T) == typeof(String))
    {
        return element.Attribute(attribute).Value;
    }

    if(typeof(T) == typeof(ItemLookupType))
    {
        return Enum.Parse(typeof(T), element.Attribute(attribute).Value);
    }
}

Ou mieux encore:

public static Int32 ParseAsInt32(this XElement element, string attribute)
{
    return Int32.Parse(element.Attribute(attribute).Value);
}

// etc, repeat for each type

Cette deuxième approche présente l’avantage supplémentaire d’avoir une probabilité beaucoup plus grande d’être en ligne, et elle évite (pour les types de valeur tels que Int32) d’éviter la nécessité d’encapsuler/décomprimer la valeur. Ces deux méthodes entraîneront une performance un peu plus rapide de la méthode.

4
Chris Shain

Vous ne savez pas si c'est exactement ce que vous voulez, mais vous pouvez utiliser les retours si vous attribuez d'abord object puis T

    public static T ParseAttributeValue<T>(this XElement element, string attribute)
    {
        if (typeof(T) == typeof(Int32))
        {
            return (T)(object)Int32.Parse(element.Attribute(attribute).Value);
        }

        if (typeof(T) == typeof(Double))
        {
            return (T)(object)Double.Parse(element.Attribute(attribute).Value);
        }

        if (typeof(T) == typeof(String))
        {
            return (T)(object)element.Attribute(attribute).Value;
        }

        return default(T);
    }

Cependant, vous devez toujours fournir T au moment de la compilation, en appelant la méthode comme suit:

int value = element.ParseAttributeValue<int>("attribute");
2
CodingWithSpike

Voici deux façons de le faire ...

    static T ReadSetting<T>(string value)
    {
        object valueObj = null;
        if (typeof(T) == typeof(Int32))
            valueObj = Int32.Parse(value);
        return (T)valueObj;
    }
    static dynamic ReadSetting2<T>(string value)
    {
        if (typeof(T) == typeof(Int32))
            return Int32.Parse(value);
        throw new UnsupportedException("Type is unsupported");
    }
    static void Main(string[] args)
    {
        int val1 = ReadSetting<Int32>("2");
        int val2 = ReadSetting2<Int32>("3");
    }
2
MrWednesday

Avec les modèles C++, ce genre de chose fonctionnerait, mais seulement si chaque code était dans une spécialisation différente et distincte. Ce qui fait que les choses fonctionnent, c'est que les modèles de fonctions non utilisés ne sont pas compilés (ou plus précisément: pas totalement instanciés). Par conséquent, le fait qu'un morceau de code ne soit pas valide si cette copie du modèle était instanciée avec un type différent montez.

C # est différent et, autant que je sache, il n’ya pas de spécialisation pour les génériques. Une façon d'accomplir ce que vous essayez de faire, tout en respectant les limitations de C #, consiste à créer une fonction avec un type de retour plus abstrait et à utiliser ParseAttributeValue uniquement pour le convertir en T.

Donc vous auriez:

private static Object AbstractParseValue(System.Type t, XElement element, string attribute)

et

public static T ParseAttributeValue<T>(this XElement element, string attribute)
{
     return (T)AbstractParseValue(typeof(T), element, attribute);
}
1
Dennis

Je suggèrerais que plutôt que de tester le paramètre type à chaque exécution de la routine, vous devriez créer une classe statique générique, comme ceci:

 classe statique interne ElementParser <T> 
 {
 public statique Func <XElement, chaîne, T> Convert = InitConvert; 

 T DefaultConvert (élément XElement, attribut de chaîne) 
 {
 renvoyer défaut (T); // Ou peut-être jeter une exception, ou autre chose 
 } 

 T InitConvert (élément XElement, attribut de chaîne) 
 {
 if (ElementParser <int> .Convert == ElementParser <int> .InitConvert) 
 {// Première fois ici pour n'importe quel type 
 Convert = DefaultConvert; // Peut écraser cette tâche ci-dessous 
 ElementParser <int> .Convert = 
 (Élément XElement, attribut de chaîne) => 
 Int32.Parse (element.Attribute (attribut) .Value); 
 ElementParser <double> .Convert = 
 (Élément XElement, attribut de chaîne) => 
 Int32.Parse (element.Attribute (attribut) .Value); 
 // etc. pour les autres types 
 } 
 else // Nous avons fait d'autres types, mais pas ce type, et nous ne faisons rien de bien Nice pour cela 
 {
 Convert = DefaultConvert; 
 } 
 return Convert (élément, attribut); 
 } 
} 
 public static T ParseAttributeValue (cet élément XElement, attribut de chaîne) 
 {
 ElementParser <T> .Convert (élément, attribut); 
} 

En utilisant cette approche, il ne sera nécessaire de faire un traitement spécial que la première fois qu'un type particulier est utilisé. Ensuite, la conversion peut être effectuée à l'aide d'un seul appel de délégué générique. Once pourrait facilement ajouter un nombre quelconque de types, et même permettre aux convertisseurs d'être enregistrés pour tout type souhaité au moment de l'exécution.

0
supercat