95 votes

Comment faire une spécialisation de modèle en C#

Comment faire de la spécialisation en C# ?

Je vais poser un problème. Vous avez un type de modèle, vous n'avez aucune idée de ce que c'est. Mais vous savez si c'est un dérivé de XYZ vous voulez appeler .alternativeFunc() . Un bon moyen est d'appeler une fonction ou une classe spécialisée et de faire en sorte que normalCall retourner .normalFunc() tout en ayant l'autre spécialisation sur tout type dérivé de XYZ d'appeler .alternativeFunc() . Comment le faire en C# ?

92voto

Marc Gravell Points 482669

En C#, ce qui se rapproche le plus de la spécialisation est l'utilisation d'une surcharge plus spécifique ; cependant, cela est fragile et ne couvre pas toutes les utilisations possibles. Par exemple :

void Foo<T>(T value) {Console.WriteLine("General method");}
void Foo(Bar value) {Console.WriteLine("Specialized method");}

Ici, si le compilateur connaît les types à la compilation, il choisira le plus spécifique :

Bar bar = new Bar();
Foo(bar); // uses the specialized method

Cependant....

void Test<TSomething>(TSomething value) {
    Foo(value);
}

utilisera Foo<T> même pour TSomething=Bar car elle est intégrée au moment de la compilation.

Une autre approche consiste à utiliser le test de type. sur une méthode générique - cependant, c'est généralement une mauvaise idée, et ce n'est pas recommandé.

En fait, le C# ne veut pas que vous travailliez avec des spécialisations, sauf pour le polymorphisme :

class SomeBase { public virtual void Foo() {...}}
class Bar : SomeBase { public override void Foo() {...}}

Ici Bar.Foo se résoudra toujours à la bonne dérogation.

0 votes

C'est encore pire si vous voulez faire cela pour des interfaces. Par exemple. Foo(IBar value) ne sera pas appelé pour une Bar .

0 votes

En fait, la spécialisation des modèles est possible au prix d'un simple appel virtuel. En particulier, cela fonctionne aussi à l'intérieur d'une fonction générique. Voir ma réponse ci-dessous : stackoverflow.com/a/29379250/1520078

67voto

Tomas Petricek Points 118959

Si l'on suppose que vous parlez de la spécialisation des modèles telle qu'elle peut être réalisée avec les modèles C++, une telle fonctionnalité n'est pas vraiment disponible en C#. Cela s'explique par le fait que les génériques C# ne sont pas traités lors de la compilation et qu'il s'agit plutôt d'une fonctionnalité du runtime.

Cependant, vous pouvez obtenir un effet similaire en utilisant les méthodes d'extension de C# 3.0. Voici un exemple qui montre comment ajouter une méthode d'extension seulement pour MyClass<int> ce qui revient à la spécialisation des modèles. Notez cependant que vous ne pouvez pas l'utiliser pour cacher l'implémentation par défaut de la méthode, car le compilateur C# préfère toujours les méthodes standard aux méthodes d'extension :

class MyClass<T> {
  public int Foo { get { return 10; } }
}
static class MyClassSpecialization {
  public static int Bar(this MyClass<int> cls) {
    return cls.Foo + 20;
  }
}

Maintenant tu peux écrire ça :

var cls = new MyClass<int>();
cls.Bar();

Si vous voulez avoir un cas par défaut pour la méthode qui serait utilisée lorsqu'aucune spécialisation n'est fournie, je crois qu'il faut écrire une méthode générique. Bar La méthode d'extension devrait faire l'affaire :

  public static int Bar<T>(this MyClass<T> cls) {
    return cls.Foo + 42;
  }

0 votes

La propriété Foo contre la méthode Bar... ne semble pas vraiment être une spécialisation typique...

2 votes

Non, ce n'est pas une spécification typique, mais c'est la seule chose facile que vous pouvez faire... (AFAIK)

3 votes

Cela semble également fonctionner sans utiliser les méthodes d'extension - juste static qui prennent un type générique. En d'autres termes, le problème signalé dans la réponse de @MarcGravell semble pouvoir être contourné en "modélisant" la méthode sur la base d'un argument tel que MyClass<T> / MyClass<int> plutôt que de modéliser la méthode en fonction du type de "données" spécifique ( T / int ).

21voto

Barend Gehrels Points 620

En ajoutant une classe intermédiaire et un dictionnaire, la spécialisation est possible .

Pour spécialiser sur T, nous créons une interface générique, ayant une méthode appelée (par exemple) Apply. Pour les classes spécifiques, cette interface est implémentée, définissant la méthode Apply spécifique à cette classe. Cette classe intermédiaire est appelée la classe de traits.

Cette classe de traits peut être spécifiée comme paramètre dans l'appel de la méthode générique, qui prend alors (bien sûr) toujours la bonne implémentation.

Au lieu de la spécifier manuellement, la classe de traits peut également être stockée dans un fichier global de type IDictionary<System.Type, object> . Il peut ensuite être recherché et voilà, vous avez une véritable spécialisation.

Si cela vous convient, vous pouvez l'exposer dans une méthode d'extension.

class MyClass<T>
{
    public string Foo() { return "MyClass"; }
}

interface BaseTraits<T>
{
    string Apply(T cls);
}

class IntTraits : BaseTraits<MyClass<int>>
{
    public string Apply(MyClass<int> cls)
    {
        return cls.Foo() + " i";
    }
}

class DoubleTraits : BaseTraits<MyClass<double>>
{
    public string Apply(MyClass<double> cls)
    {
        return cls.Foo() + " d";
    }
}

// Somewhere in a (static) class:
public static IDictionary<Type, object> register;
register = new Dictionary<Type, object>();
register[typeof(MyClass<int>)] = new IntTraits();
register[typeof(MyClass<double>)] = new DoubleTraits();

public static string Bar<T>(this T obj)
{
    BaseTraits<T> traits = register[typeof(T)] as BaseTraits<T>;
    return traits.Apply(obj);
}

var cls1 = new MyClass<int>();
var cls2 = new MyClass<double>();

string id = cls1.Bar();
string dd = cls2.Bar();

Voir ceci lien Pour une description détaillée et des exemples, consultez mon récent blog et les articles qui suivent.

0 votes

C'est le Factory Pattern et c'est un moyen décent de traiter algunos des lacunes des génériques

1 votes

@Yaur Je ressemble à un modèle de décorateur de manuel pour moi.

5voto

GregC Points 4679

Certaines des réponses proposées consistent à utiliser des informations sur les types au moment de l'exécution : elles sont intrinsèquement plus lentes que les appels de méthode liés au moment de la compilation.

Le compilateur n'applique pas la spécialisation aussi bien qu'il le fait en C++.

Je recommanderais de regarder PostSharp pour un moyen d'injecter du code après que le compilateur habituel ait fait son travail pour obtenir un effet similaire au C++.

-1voto

Jeroen Landheer Points 3346

Si vous voulez simplement tester si un type est dérivé de XYZ, alors vous pouvez utiliser :

theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));

Si c'est le cas, vous pouvez transformer "theunknownobject" en XYZ et invoquer alternativeFunc() comme ceci :

XYZ xyzObject = (XYZ)theunknownobject; 
xyzObject.alternativeFunc();

J'espère que cela vous aidera.

1 votes

Je ne connais pas beaucoup le C#, mais celui qui a voté pour vous aurait dû dire pourquoi. Je n'ai aucune idée de ce qui ne va pas dans votre réponse ou si quelque chose ne va pas avec elle.

0 votes

Je ne sais pas non plus. Ça me semble assez valide. Bien qu'un peu plus verbeux que nécessaire.

3 votes

Ce n'était pas moi, mais c'est parce que la réponse n'a rien à voir avec la question. Consultez le site "c++ template specialization"

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