Existe-t-il un moyen en C # de remplacer une méthode de classe par une méthode d'extension?

98

Il y a eu des occasions où je voudrais remplacer une méthode dans une classe avec une méthode d'extension. Existe-t-il un moyen de le faire en C #?

Par exemple:

public static class StringExtension
{
    public static int GetHashCode(this string inStr)
    {
        return MyHash(inStr);
    }
}

Un cas où j'ai voulu faire cela est de pouvoir stocker un hachage d'une chaîne dans une base de données et que cette même valeur soit utilisée par toutes les classes qui utilisent le hachage de la classe de chaîne (c'est-à-dire Dictionary, etc.) L'algorithme de hachage .Net intégré n'est pas garanti pour être compatible d'une version du Framework à l'autre, je souhaite le remplacer par le mien.

Il y a un autre cas dans lequel je voudrais remplacer une méthode de classe avec une méthode d'extension afin qu'il ne soit pas seulement spécifique à la classe de chaîne ou à la méthode GetHashCode.

Je sais que je pourrais le faire en sous-classant une classe existante, mais ce serait pratique de pouvoir le faire avec une extension dans de nombreux cas.

Phred Menyhert
la source
Puisqu'un dictionnaire est une structure de données en mémoire, quelle différence cela fait-il que l'algorithme de hachage change d'une version du framework à la suivante? Si la version du framework change, alors évidemment l'application a été redémarrée et le dictionnaire a été reconstruit.
David Nelson

Réponses:

91

Non; une méthode d'extension n'a jamais priorité sur une méthode d'instance avec une signature appropriée, et ne participe jamais au polymorphisme ( GetHashCodec'est une virtualméthode).

Marc Gravell
la source
"... ne participe jamais au polymorphisme (GetHashCode est une méthode virtuelle)" Pourriez-vous expliquer d'une autre manière pourquoi une méthode d'extension ne participerait jamais au polymorphisme parce qu'elle est virtuelle? J'ai du mal à voir le lien avec ces deux déclarations.
Alex
3
@Alex en mentionnant virtuel je clarifie simplement ce que signifie être polymorphe. Dans pratiquement toutes les utilisations de GetHashCode, le type concret est inconnu - le polymorphisme est donc en jeu. En tant que telles, les méthodes d'extension n'aideraient pas même si elles étaient prioritaires dans le compilateur normal. Ce que l'OP veut vraiment, c'est le patching de singe. Quels c # et .net ne facilitent pas.
Marc Gravell
4
C'est triste, en C # tant de méthodes non sens trompeuses, comme par exemple .ToString()dans les tableaux. C'est certainement une fonctionnalité nécessaire.
Hi-Angel le
Pourquoi n'obtenez-vous pas une erreur de compilation alors? Par exemple, je tape. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } }
LowLevel
@LowLevel pourquoi le feriez-vous?
Marc Gravell
7

Si la méthode a une signature différente, cela peut être fait - donc dans votre cas: non.

Mais sinon, vous devez utiliser l'héritage pour faire ce que vous recherchez.

Chris Brandsma
la source
2

Autant que je sache, la réponse est non, car une méthode d'extension n'est pas une instance, c'est plutôt une fonction intellisense qui vous permet d'appeler une méthode statique en utilisant une instance d'une classe. Je pense qu'une solution à votre problème peut être un intercepteur qui intercepte l'exécution d'une méthode spécifique (par exemple GetHashCode ()) et fait autre chose.Pour utiliser un tel intercepteur (comme celui fourni par Castle Project), tous les objets doivent être instansiés à l'aide d'un object factory (ou un conteneur IoC dans Castle) afin que leurs interfaces puissent être interceptées via un proxy dynamique généré au runtime (Caslte vous permet également d'intercepter les membres virtuels des classes)

Beatles1692
la source
0

J'ai trouvé un moyen d'invoquer une méthode d'extension avec la même signature qu'une méthode de classe, mais cela ne semble pas très élégant. En jouant avec les méthodes d'extension, j'ai remarqué un comportement non documenté. Exemple de code:

public static class TestableExtensions
{
    public static string GetDesc(this ITestable ele)
    {
        return "Extension GetDesc";
    }

    public static void ValDesc(this ITestable ele, string choice)
    {
        if (choice == "ext def")
        {
            Console.WriteLine($"Base.Ext.Ext.GetDesc: {ele.GetDesc()}");
        }
        else if (choice == "ext base" && ele is BaseTest b)
        {
            Console.WriteLine($"Base.Ext.Base.GetDesc: {b.BaseFunc()}");
        }
    }

    public static string ExtFunc(this ITestable ele)
    {
        return ele.GetDesc();
    }

    public static void ExtAction(this ITestable ele, string choice)
    {
        ele.ValDesc(choice);
    }
}

public interface ITestable
{

}

public class BaseTest : ITestable
{
    public string GetDesc()
    {
        return "Base GetDesc";
    }

    public void ValDesc(string choice)
    {
        if (choice == "")
        {
            Console.WriteLine($"Base.GetDesc: {GetDesc()}");
        }
        else if (choice == "ext")
        {
            Console.WriteLine($"Base.Ext.GetDesc: {this.ExtFunc()}");
        }
        else
        {
            this.ExtAction(choice);
        }
    }

    public string BaseFunc()
    {
        return GetDesc();
    }
}

Ce que j'ai remarqué, c'est que si j'appelais une deuxième méthode à partir d'une méthode d'extension, elle appellerait la méthode d'extension qui correspondait à la signature même s'il y avait une méthode de classe qui correspondait également à la signature. Par exemple dans le code ci-dessus, lorsque j'appelle ExtFunc (), qui à son tour appelle ele.GetDesc (), j'obtiens la chaîne de retour "Extension GetDesc" au lieu de la chaîne "Base GetDesc" que nous attendons.

Tester le code:

var bt = new BaseTest();
bt.ValDesc("");
//Output is Base.GetDesc: Base GetDesc
bt.ValDesc("ext");
//Output is Base.Ext.GetDesc: Extension GetDesc
bt.ValDesc("ext def");
//Output is Base.Ext.Ext.GetDesc: Extension GetDesc
bt.ValDesc("ext base");
//Output is Base.Ext.Base.GetDesc: Base GetDesc

Cela vous permet de rebondir entre les méthodes de classe et les méthodes d'extension à volonté, mais nécessite l'ajout de méthodes "pass-through" en double pour vous amener dans la "portée" que vous désirez. J'appelle cela portée ici faute d'un meilleur mot. J'espère que quelqu'un pourra me dire comment cela s'appelle réellement.

Vous avez peut-être deviné par mes noms de méthodes «pass-through» que j'ai aussi caressé l'idée de leur passer des délégués dans l'espoir qu'une seule méthode ou deux puisse agir comme un relais pour plusieurs méthodes avec la même signature. Malheureusement, ce n'était pas comme une fois que le délégué a été décompressé, il a toujours choisi la méthode de classe plutôt que la méthode d'extension, même à partir d'une autre méthode d'extension. "Portée" n'avait plus d'importance. Cependant, je n'ai pas beaucoup utilisé les délégués Action et Func, alors peut-être que quelqu'un de plus expérimenté pourrait comprendre cette partie.

Humide
la source