web-dev-qa-db-fra.com

Comment faire la spécialisation des modèles en C #

Comment feriez-vous une 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 dérivé de XYZ vous voulez appeler .alternativeFunc(). Un excellent moyen est d'appeler une fonction ou une classe spécialisée et d'avoir normalCall return .normalFunc() tout en ayant l'autre spécialisation sur tout type dérivé de XYZ pour appeler .alternativeFunc(). Comment cela se ferait-il en C #?

80
user34537

En C #, le plus proche de la spécialisation est d'utiliser 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 à compiler, il choisira le plus spécifique:

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

Toutefois....

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

utilisera Foo<T> même pour TSomething=Bar, car il est gravé au moment de la compilation.

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

Fondamentalement, C # ne veut tout simplement 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 résoudra toujours le remplacement correct.

83
Marc Gravell

En supposant que vous parlez de spécialisation de modèles car cela peut être fait avec des modèles C++ - une fonctionnalité comme celle-ci n'est pas vraiment disponible en C #. Cela est dû au fait que les génériques C # ne sont pas traités lors de la compilation et sont plutôt une fonctionnalité de l'exécution.

Cependant, vous pouvez obtenir un effet similaire à l'aide des méthodes d'extension C # 3.0. Voici un exemple qui montre comment ajouter une méthode d'extension uniquement pour MyClass<int> type, qui ressemble à la spécialisation des modèles. Notez cependant que vous ne pouvez pas l'utiliser pour masquer 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;
  }
}

Vous pouvez maintenant écrire ceci:

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

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

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

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

Pour nous 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 dont l'interface est implémentée, définissez la méthode Apply spécifique à cette classe. Cette classe intermédiaire est appelée la classe des traits.

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

Au lieu de la spécifier manuellement, la classe de traits peut également être stockée dans un IDictionary<System.Type, object> Global. Il peut alors être recherché et le tour est joué, vous y avez une réelle 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 vers mon blog récent et les suivis pour une description détaillée et des exemples.

15
Barend Gehrels

Je cherchais également un modèle pour simuler la spécialisation des modèles. Certaines approches peuvent fonctionner dans certaines circonstances. Mais qu'en est-il de l'affaire

static void Add<T>(T value1, T value2)
{
    //add the 2 numeric values
}

Il serait possible de choisir l'action en utilisant des instructions, par ex. if (typeof(T) == typeof(int)). Mais il existe un meilleur moyen de simuler la spécialisation de modèles réels avec la surcharge d'un seul appel de fonction virtuelle:

public interface IMath<T>
{
    T Add(T value1, T value2);
}

public class Math<T> : IMath<T>
{
    public static readonly IMath<T> P = Math.P as IMath<T> ?? new Math<T>();

    //default implementation
    T IMath<T>.Add(T value1, T value2)
    {
        throw new NotSupportedException();    
    }
}

class Math : IMath<int>, IMath<double>
{
    public static Math P = new Math();

    //specialized for int
    int IMath<int>.Add(int value1, int value2)
    {
        return value1 + value2;
    }

    //specialized for double
    double IMath<double>.Add(double value1, double value2)
    {
        return value1 + value2;
    }
}

Maintenant, nous pouvons écrire, sans avoir à connaître le type à l'avance:

static T Add<T>(T value1, T value2)
{
    return Math<T>.P.Add(value1, value2);
}

private static void Main(string[] args)
{
    var result1 = Add(1, 2);
    var result2 = Add(1.5, 2.5);

    return;
}

Si la spécialisation devait non seulement être appelée pour les types implémentés, mais aussi pour les types dérivés, on pourrait utiliser un paramètre In pour l'interface. Cependant, dans ce cas, les types de retour des méthodes ne peuvent plus être du type générique T.

11
LionAM

Certaines des réponses proposées utilisent des informations de type à l'exécution: intrinsèquement plus lentes que les appels de méthode liés à la compilation.

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

Je recommanderais de regarder PostSharp pour trouver un moyen d'injecter du code après que le compilateur habituel soit fait pour obtenir un effet similaire à C++.

6
GregC

Je pense qu'il existe un moyen d'y parvenir avec .NET 4+ en utilisant la résolution dynamique:

static class Converter<T>
{
    public static string Convert(T data)
    {
        return Convert((dynamic)data);
    }

    private static string Convert(Int16 data) => $"Int16 {data}";
    private static string Convert(UInt16 data) => $"UInt16 {data}";
    private static string Convert(Int32 data) => $"Int32 {data}";
    private static string Convert(UInt32 data) => $"UInt32 {data}";
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Converter<Int16>.Convert(-1));
        Console.WriteLine(Converter<UInt16>.Convert(1));
        Console.WriteLine(Converter<Int32>.Convert(-1));
        Console.WriteLine(Converter<UInt32>.Convert(1));
    }
}

Sortie:

Int16 -1
UInt16 1
Int32 -1
UInt32 1

Ce qui montre qu'une implémentation différente est appelée pour différents types.

5
Drolevar

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

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

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

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

J'espère que cela t'aides.

0
Jeroen Landheer