Есть ли способ в C # переопределить метод класса с помощью метода расширения?

98

Были случаи, когда я хотел переопределить метод в классе с помощью метода расширения. Есть ли способ сделать это на C #?

Например:

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

Случай, когда я хотел это сделать, - это иметь возможность хранить хэш строки в базе данных и иметь то же значение, которое будет использоваться всеми классами, которые используют хэш класса строки (например, словарь и т. Д.). не гарантируется, что встроенный алгоритм хеширования .Net будет совместим от одной версии Framework к другой, я хочу заменить его своей собственной.

Есть другой случай, в котором я столкнулся, когда я хотел бы переопределить метод класса с помощью метода расширения, чтобы он не был специфичным только для строкового класса или метода GetHashCode.

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

Фред Менихерт
источник
Поскольку Dictionary - это структура данных в памяти, какая разница, будет ли алгоритм хеширования изменяться с одной версии платформы на другую? Если версия фреймворка изменилась, то очевидно, что приложение было перезапущено, а словарь перестроен.
Дэвид Нельсон

Ответы:

92

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

Марк Гравелл
источник
«... никогда не участвует в полиморфизме (GetHashCode - виртуальный метод)» Не могли бы вы объяснить другим способом, почему метод расширения никогда не участвует в полиморфизме из-за того, что он виртуальный? У меня проблемы с просмотром связи между этими двумя утверждениями.
Alex
3
@Alex, упоминая виртуальный, я просто разъясняю, что значит быть полиморфным. Практически во всех случаях использования GetHashCode конкретный тип неизвестен, поэтому здесь присутствует полиморфизм. Таким образом, методы расширения не помогли бы, даже если бы они имели приоритет в обычном компиляторе. Что действительно нужно OP, так это исправление обезьян. Какие 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 09
@LowLevel с чего бы?
Marc Gravell
7

Если у метода другая подпись, то это можно сделать - так в вашем случае: нет.

Но в противном случае вам нужно использовать наследование, чтобы делать то, что вы ищете.

Крис Брандсма
источник
2

Насколько я знаю, ответ отрицательный, потому что метод расширения не является экземпляром. Для меня это больше похоже на средство intellisense, которое позволяет вам вызывать статический метод с использованием экземпляра класса. Я думаю, что решением вашей проблемы может быть перехватчик, который перехватывает выполнение определенного метода (например, GetHashCode ()) и делает что-то еще. Чтобы использовать такой перехватчик (например, один Castle Project), все объекты должны быть созданы с помощью объектная фабрика (или контейнер IoC в Castle), чтобы их интерфейсы могли быть перехвачены через динамический прокси, созданный во время выполнения (Caslte также позволяет вам перехватывать виртуальные члены классов)

Битлз1692
источник
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, поэтому, возможно, кто-нибудь более опытный сможет разобраться в этой части.

DanK
источник