C'è un modo in C # per sovrascrivere un metodo di classe con un metodo di estensione?


98

Ci sono state occasioni in cui vorrei sovrascrivere un metodo in una classe con un metodo di estensione. C'è un modo per farlo in C #?

Per esempio:

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

Un caso in cui ho voluto fare questo è essere in grado di memorizzare un hash di una stringa in un database e avere lo stesso valore essere utilizzato da tutte le classi che utilizzano l'hash della classe stringa (cioè Dictionary, ecc.) Poiché il Non è garantito che l'algoritmo di hashing .Net integrato sia compatibile da una versione del Framework a quella successiva, voglio sostituirlo con il mio.

Ci sono altri casi in cui mi sono imbattuto in cui vorrei sovrascrivere un metodo di classe con un metodo di estensione, quindi non è solo specifico per la classe stringa o il metodo GetHashCode.

So che potrei farlo con la sottoclasse di una classe esistente, ma sarebbe utile poterlo fare con un'estensione in molti casi.


Poiché un dizionario è una struttura dati in memoria, che differenza fa se l'algoritmo di hashing cambia da una versione del framework a quella successiva? Se la versione del framework cambia, ovviamente l'applicazione è stata riavviata e il dizionario è stato ricostruito.
David Nelson,

Risposte:


91

No; un metodo di estensione non ha mai la priorità su un metodo di istanza con una firma adatta e non partecipa mai al polimorfismo ( GetHashCodeè un virtualmetodo).


"... non partecipa mai al polimorfismo (GetHashCode è un metodo virtuale)" Potresti spiegare in un altro modo perché un metodo di estensione non dovrebbe mai partecipare al polimorfismo poiché è virtuale? Ho problemi a vedere la connessione con queste due affermazioni.
Alex

3
@Alex citando virtuale sto semplicemente chiarendo cosa significa essere polimorfici. In quasi tutti gli usi di GetHashCode, il tipo di cemento è sconosciuto, quindi è in gioco il polimorfismo. In quanto tali, i metodi di estensione non sarebbero utili anche se avessero la priorità nel compilatore normale. Ciò che l'OP vuole veramente è il patching delle scimmie. Quale c # e .net non facilitano.
Marc Gravell

4
È triste, in C # così tanti metodi fuorvianti senza senso, come ad esempio .ToString()negli array. È sicuramente una caratteristica necessaria.
Hi-Angel

Perché allora non ricevi un errore del compilatore? Ad esempio, digito. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } }
LowLevel

@LowLevel perché dovresti?
Marc Gravell

7

Se il metodo ha una firma diversa, allora può essere fatto, quindi nel tuo caso: no.

Ma per il resto devi usare l'ereditarietà per fare quello che stai cercando.


2

Per quanto ne so, la risposta è no, perché un metodo di estensione non è un'istanza, è più simile a una struttura intellisense che ti consente di chiamare un metodo statico utilizzando un'istanza di una classe. Penso che una soluzione al tuo problema possa essere un intercettore che intercetta l'esecuzione di un metodo specifico (ad esempio GetHashCode ()) e fa qualcos'altro. object factory (o un contenitore IoC in Castle) in modo che le loro interfacce possano essere intercettate attraverso un proxy dinamico generato in runtime (Caslte ti consente anche di intercettare membri virtuali delle classi)


0

Ho trovato un modo per invocare un metodo di estensione con la stessa firma di un metodo di classe, tuttavia non sembra molto elegante. Quando ho giocato con i metodi di estensione ho notato alcuni comportamenti non documentati. Codice di esempio:

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();
    }
}

Quello che ho notato è che se chiamassi un secondo metodo dall'interno di un metodo di estensione, chiamerebbe il metodo di estensione che corrisponde alla firma anche se ci fosse un metodo di classe che corrispondeva anche alla firma. Ad esempio nel codice sopra, quando chiamo ExtFunc (), che a sua volta chiama ele.GetDesc (), ottengo la stringa di ritorno "Extension GetDesc" invece della stringa "Base GetDesc" che ci aspetteremmo.

Testare il codice:

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

Ciò consente di rimbalzare avanti e indietro tra metodi di classe e metodi di estensione a piacimento, ma richiede l'aggiunta di metodi "pass-through" duplicati per entrare nello "ambito" desiderato. Lo chiamo ambito qui per mancanza di una parola migliore. Spero che qualcuno possa farmi sapere come si chiama effettivamente.

Potreste aver intuito dai miei nomi di metodo "pass-through" che ho anche accarezzato l'idea di passare loro delegati nella speranza che un singolo metodo o due potessero fungere da pass-through per più metodi con la stessa firma. Sfortunatamente non doveva essere come una volta che il delegato è stato decompresso, ha sempre scelto il metodo di classe rispetto al metodo di estensione anche dall'interno di un altro metodo di estensione. "Scope" non aveva più importanza. Non ho usato molto i delegati di Action e Func, quindi forse qualcuno più esperto potrebbe capire quella parte.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.