Czy istnieje sposób w języku C #, aby zastąpić metodę klasy metodą rozszerzającą?

98

Zdarzały się sytuacje, w których chciałbym przesłonić metodę w klasie z metodą rozszerzającą. Czy jest jakiś sposób, aby to zrobić w C #?

Na przykład:

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

Przykładem, w którym chciałem to zrobić, jest możliwość przechowywania skrótu ciągu w bazie danych i posiadania tej samej wartości, która będzie używana przez wszystkie klasy, które używają skrótu klasy ciągu (np. Dictionary itp.). nie ma gwarancji, że wbudowany algorytm haszowania .Net będzie kompatybilny z jedną wersją Framework do następnej, chcę go zastąpić własną.

Są inne przypadki, z którymi się spotkałem, w których chciałbym zastąpić metodę klasy metodą rozszerzającą, więc nie jest ona specyficzna tylko dla klasy string lub metody GetHashCode.

Wiem, że mógłbym to zrobić z podklasą istniejącej klasy, ale w wielu przypadkach byłoby przydatne móc to zrobić z rozszerzeniem.

Phred Menyhert
źródło
Ponieważ słownik jest strukturą danych w pamięci, jaką różnicę robi to, czy algorytm mieszania zmienia się z jednej wersji platformy na następną? Jeśli wersja frameworka ulegnie zmianie, oznacza to, że aplikacja została zrestartowana, a słownik odbudowano.
David Nelson

Odpowiedzi:

92

Nie; metoda rozszerzająca nigdy nie ma pierwszeństwa przed metodą instancji z odpowiednim podpisem i nigdy nie uczestniczy w polimorfizmie ( GetHashCodejest virtualmetodą).

Marc Gravell
źródło
„… nigdy nie uczestniczy w polimorfizmie (GetHashCode jest metodą wirtualną)” Czy możesz wyjaśnić w inny sposób, dlaczego metoda rozszerzająca nigdy nie uczestniczyłaby w polimorfizmie, skoro jest wirtualna? Mam problemy z dostrzeżeniem związku z tymi dwoma stwierdzeniami.
Alex
3
@Alex, wspominając o wirtualnym, po prostu wyjaśniam, co to znaczy być polimorficznym. W praktycznie wszystkich zastosowaniach GetHashCode konkretny typ jest nieznany - w grę wchodzi więc polimorfizm. W związku z tym metody rozszerzające nie pomogłyby, nawet gdyby miały priorytet w zwykłym kompilatorze. To, czego naprawdę chce OP, to łatanie małp. Które C # i .net nie ułatwiają.
Marc Gravell
4
To smutne, w C # jest tak wiele wprowadzających w błąd metod bezsensownych, jak np. .ToString()W tablicach. Jest to zdecydowanie potrzebna funkcja.
Hi-Angel
Dlaczego więc nie pojawia się błąd kompilatora? Np. Piszę. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } }
LowLevel
@LowLevel, dlaczego miałbyś?
Marc Gravell
7

Jeśli metoda ma inny podpis, to można to zrobić - czyli w Twoim przypadku: nie.

Ale w przeciwnym razie musisz użyć dziedziczenia, aby zrobić to, czego szukasz.

Chris Brandsma
źródło
2

O ile wiem, odpowiedź brzmi: nie, ponieważ metoda rozszerzająca nie jest instancją, a dla mnie bardziej przypomina funkcję Intellisense, która pozwala wywołać metodę statyczną przy użyciu instancji klasy. Myślę, że rozwiązaniem twojego problemu może być przechwytywacz, który przechwytuje wykonanie określonej metody (np. GetHashCode ()) i zrobi coś innego. Aby użyć takiego przechwytywacza (takiego jak ten, który zapewnia Castle Project), wszystkie obiekty powinny być instansowane za pomocą fabryka obiektów (lub kontener IoC w Castle), dzięki czemu ich interfejsy mogą być przechwytywane przez dynamiczne proxy generowane w czasie wykonywania (Caslte umożliwia również przechwytywanie wirtualnych członków klas)

Beatles1692
źródło
0

Znalazłem sposób na wywołanie metody rozszerzającej z taką samą sygnaturą jak metoda klasowa, jednak nie wydaje się ona zbyt elegancka. Podczas zabawy z metodami rozszerzającymi zauważyłem nieudokumentowane zachowanie. Przykładowy kod:

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

Zauważyłem, że gdybym wywołał drugą metodę z wnętrza metody rozszerzającej, wywołałaby metodę rozszerzenia, która pasowała do podpisu, nawet jeśli istniałaby metoda klasy, która również pasuje do podpisu. Na przykład w powyższym kodzie, kiedy wywołuję ExtFunc (), który z kolei wywołuje ele.GetDesc (), otrzymuję zwrotny ciąg znaków „Extension GetDesc” zamiast ciągu „Base GetDesc”, którego byśmy się spodziewali.

Testowanie kodu:

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

Pozwala to na dowolne przechodzenie między metodami klas i metodami rozszerzającymi, ale wymaga dodania zduplikowanych metod „przekazujących”, aby uzyskać pożądany „zakres”. Nazywam to zakresem z braku lepszego słowa. Mam nadzieję, że ktoś może mi powiedzieć, jak to się właściwie nazywa.

Mogłeś odgadnąć na podstawie moich nazw metod „tranzytowych”, którymi również bawiłem się pomysłem przekazywania im delegatów w nadziei, że jedna lub dwie metody mogą działać jako przejście dla wielu metod z tym samym podpisem. Niestety nie miało to być tak, że po rozpakowaniu delegata zawsze wybierał metodę klasy zamiast metody rozszerzającej, nawet z wnętrza innej metody rozszerzającej. „Zakres” nie miał już znaczenia. Nie korzystałem zbytnio z delegatów Action i Func, więc może ktoś bardziej doświadczony mógłby to rozgryźć.

Wilgotny
źródło