Има ли някакъв начин в C# да замените метод на клас с метод на разширение?

Имало е случаи, в които бих искал да заменя метод в клас с метод за разширение. Има ли някакъв начин да направите това в C#?

Например:

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

Случай, в който исках да направя това, е да мога да съхраня хеш на низ в база данни и същата стойност да се използва от всички класове, които използват хеша на класа на низа (т.е. речник и т.н.), тъй като Не е гарантирано, че вграденият алгоритъм за хеширане на .Net е съвместим от една версия на Framework до друга, искам да го заменя със своя собствена.

Има и други случаи, в които бих искал да заместя метод на клас с метод на разширение, така че да не е специфичен само за класа на низа или метода GetHashCode.

Знам, че мога да направя това с подкласиране на съществуващ клас, но би било удобно да мога да го направя с разширение в много случаи.


person Phred Menyhert    schedule 22.05.2009    source източник
comment
Тъй като речникът е структура от данни в паметта, каква е разликата дали алгоритъмът за хеширане се променя от една версия на рамката към следващата? Ако версията на рамката се промени, тогава очевидно приложението е рестартирано и речникът е изграден отново.   -  person David Nelson    schedule 22.05.2009


Отговори (4)


Не; методът на разширение никога няма приоритет пред метод на екземпляр с подходящ подпис и никога не участва в полиморфизъм (GetHashCode е virtual метод).

person Marc Gravell    schedule 22.05.2009
comment
...никога не участва в полиморфизъм (GetHashCode е виртуален метод) Бихте ли обяснили по друг начин защо метод за разширение никога не би участвал в полиморфизъм, тъй като е виртуален? Имам проблем да видя връзката с тези две твърдения. - person Alex; 31.05.2014
comment
@Alex, като споменах виртуален, просто изяснявам какво означава да си полиморфен. При почти всички употреби на GetHashCode конкретният тип е неизвестен - така че има полиморфизъм. Като такива, методите за разширение не биха помогнали дори ако имат приоритет в обикновения компилатор. Това, което ОП наистина иска, е маймунджийство. Които c# и .net не улесняват. - person Marc Gravell; 31.05.2014
comment
Това е тъжно, в C# толкова много подвеждащи безсмислени методи, като напр. .ToString() в масиви. Това определено е необходима функция. - person Hi-Angel; 02.09.2015
comment
Защо тогава не получавате грешка на компилатора? напр. аз пиша. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } } - person LowLevel; 09.06.2018
comment
@LowLevel защо бихте? - person Marc Gravell; 09.06.2018

Ако методът има различен подпис, тогава може да се направи -- така че във вашия случай: не.

Но в противен случай трябва да използвате наследяване, за да направите това, което търсите.

person Chris Brandsma    schedule 22.05.2009

Доколкото знам, отговорът е не, защото методът за разширение не е екземпляр. За мен това е по-скоро интелектуално средство, което ви позволява да извикате статичен метод, използвайки екземпляр на клас. Мисля, че решение на вашия проблем може да бъде прихващач, който прихваща изпълнението на конкретен метод (напр. GetHashCode()) и да прави нещо друго. За да използвате такъв прихващач (като този, който предлага Castle Project), всички обекти трябва да бъдат инстанцирани с помощта на обектна фабрика (или IoC контейнер в Castle), така че техните интерфейси да могат да бъдат прихванати чрез динамичен прокси, генериран по време на изпълнение. (Caslte също ви позволява да прихващате виртуални членове на класове)

person Beatles1692    schedule 23.05.2009

Намерих начин да извикам метод на разширение със същия подпис като метод на клас, но не изглежда много елегантен. Когато си играех с методите за разширение, забелязах известно недокументирано поведение. Примерен код:

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

Това, което забелязах е, че ако извикам втори метод от вътрешността на метод на разширение, той ще извика метода на разширение, който съответства на подписа, дори ако има метод на клас, който също съответства на подписа. Например в кода по-горе, когато извикам ExtFunc(), който от своя страна извиква ele.GetDesc(), получавам върнатия низ „Extension GetDesc“ вместо низа „Base GetDesc“, който бихме очаквали.

Тестване на кода:

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

Това ви позволява да прескачате напред-назад между методите на класа и методите за разширение по желание, но изисква добавянето на дублиращи се "преминаващи" методи, за да влезете в "обхвата", който желаете. Тук го наричам обхват поради липса на по-добра дума. Надяваме се, че някой може да ми каже как всъщност се казва.

Може би сте се досетили по имената на моите "преминаващи" методи, че също си играех с идеята да им предам делегати с надеждата, че един или два метода могат да действат като преминаване за множество методи с еднакъв подпис. За съжаление това не трябваше да бъде така, тъй като след като делегатът беше разопакован, той винаги избираше метода на класа пред метода на разширение дори от друг метод на разширение. „Обхватът“ вече нямаше значение. Въпреки това не съм използвал много делегати на Action и Func, така че може би някой по-опитен би могъл да разбере тази част.

person DanK    schedule 23.03.2019