Чи є в C # спосіб замінити метод класу методом розширення?


98

Бували випадки, коли я хотів би замінити метод у класі за допомогою методу розширення. Чи можна це зробити в C #?

Наприклад:

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

Випадок, коли я хотів це зробити, це можливість зберігати хеш рядка в базі даних і мати те саме значення, яке використовуватимуть усі класи, що використовують хеш класу рядка (тобто словник тощо), оскільки вбудований алгоритм хешування .Net не гарантовано сумісний з однією версією Framework до наступної, я хочу замінити його на свій.

Є й інший випадок, коли я стикався з переходом методу класу за допомогою методу розширення, щоб він не стосувався лише рядкового класу чи методу GetHashCode.

Я знаю, що міг би зробити це, підкласувавши існуючий клас, але було б зручно мати можливість робити це з розширенням у багатьох випадках.


Оскільки словник - це структура даних в пам'яті, яка різниця, чи змінюється алгоритм хешування з версії фреймворку на наступну? Якщо версія фреймворку змінюється, то, очевидно, додаток було перезапущено і словник перероблений.
Девід Нельсон,

Відповіді:


91

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


"... ніколи не бере участі в поліморфізмі (GetHashCode - це віртуальний метод)" Чи можете ви пояснити по-іншому, чому метод розширення ніколи не брав би участі в поліморфізмі через свою віртуальність? Я маю проблеми, бачачи зв’язок із цими двома твердженнями.
Олексій

3
@Alex, згадуючи віртуальний, я просто уточнюю, що означає бути поліморфним. Практично у всіх випадках використання GetHashCode конкретний тип невідомий - отже, у поліморфізмі. Як такі, методи розширення не допомогли б, навіть якби вони мали пріоритет у звичайному компіляторі. Що справді хоче ОП, це виправлення мавп. Які c # та .net не сприяють.
Марк Гравелл

4
Це прикро, але в C # так багато оманливих безглуздих методів, як, наприклад, .ToString()у масивах. Це, безумовно, необхідна функція.
Hi-Angel

Чому б вам тоді не отримати помилку компілятора? Наприклад, я набираю. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } }
LowLevel

@LowLevel, чому б ви?
Марк Гравелл

7

Якщо метод має інший підпис, то це можна зробити - так у вашому випадку: ні.

Але в іншому випадку вам потрібно використовувати спадщину, щоб робити те, що ви шукаєте.


2

Наскільки я знаю, відповідь "ні", оскільки метод розширення не є екземпляром. Це більше схоже на об'єкт intellisense, який дозволяє вам викликати статичний метод за допомогою екземпляра класу. Я думаю, що рішенням вашої проблеми може бути перехоплювач, який перехоплює виконання певного методу (наприклад, GetHashCode ()) і робить щось інше. object factory (або IoC-контейнер у Castle), щоб їх інтерфейси могли бути перехоплені через динамічний проксі, сформований під час виконання (Caslte також дозволяє перехоплювати віртуальних членів класів)


0

Я знайшов спосіб викликати метод розширення з тим самим підписом, що і метод класу, однак він здається не дуже елегантним. Граючись із методами розширення, я помітив деяку поведінку без документації. Приклад коду:

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, хоча, можливо, хтось досвідченіший міг би зрозуміти цю частину.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.