在C#中有没有一种方法可以使用扩展方法来覆盖类方法?

110

有时候我想用扩展方法来覆盖类中的一个方法,在C#中有没有办法实现这个想法?

例如:

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

有时我想做的一件事是将字符串的哈希值存储到数据库中,并使所有使用字符串类哈希(例如字典等)的类使用相同的值。由于内置的.NET哈希算法不能保证在框架的不同版本之间兼容,因此我想用自己的算法替换它。

我遇到的其他情况也需要使用扩展方法覆盖类方法,因此这不仅特定于字符串类或GetHashCode方法。

我知道我可以通过从现有类进行子类化来实现这一点,但在许多情况下,使用扩展将非常方便。


由于字典是一种内存数据结构,所以哈希算法从一个框架版本到另一个版本的更改会有什么区别呢?如果框架版本发生更改,那么显然应用程序已经重新启动,并且字典已经被重建。 - David Nelson
4个回答

101

不会的。 扩展方法永远不会优先于具有合适签名的实例方法,并且从不参与多态性(GetHashCode是一个virtual方法)。


“...从来不参与多态性(GetHashCode是一个虚方法)”这句话的意思是扩展方法永远不会参与多态性。您能否以其他方式解释一下,为什么扩展方法由于其是虚方法而永远不会参与多态?我很难看到这两个语句之间的联系。 - Alex
3
通过提到虚拟性,我只是在澄清多态的含义。在几乎所有使用GetHashCode的情况下,具体类型都是未知的-因此多态性起作用。因此,即使扩展方法在常规编译器中具有优先级,它们也不会有所帮助。OP真正想要的是Monkey-patching。这是C#和.NET不支持的。 - Marc Gravell
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 你为什么要这样做? - Marc Gravell

8
如果方法签名不同,那么可以实现—所以在您的情况下:不行。但是,否则您需要使用继承来实现您要查找的内容。

2
据我所知,答案是否定的,因为扩展方法不是实例。对我来说,它更像是一种智能感知工具,可以让您使用类的实例调用静态方法。我认为解决您问题的方法可能是拦截器,它会拦截特定方法(例如GetHashCode()),并执行其他操作。要使用这样的拦截器(例如Castle Project提供的拦截器),所有对象都应该使用对象工厂(或Castle中的IoC容器)进行实例化,以便它们的接口可以通过在运行时生成的动态代理进行拦截。(Castle还允许您拦截类的虚成员)

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委托,因此也许更有经验的人可以解决这部分问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接