Có cách nào trong C # để ghi đè một phương thức lớp bằng một phương thức mở rộng không?


98

Đã có lúc tôi muốn ghi đè một phương thức trong một lớp bằng một phương thức mở rộng. Có cách nào để làm điều đó trong C # không?

Ví dụ:

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

Một trường hợp mà tôi muốn làm điều này là có thể lưu trữ băm của một chuỗi vào cơ sở dữ liệu và có cùng giá trị đó được sử dụng bởi tất cả các lớp sử dụng băm của lớp chuỗi (tức là Từ điển, v.v.) Thuật toán băm .Net tích hợp sẵn không đảm bảo tương thích từ phiên bản này sang phiên bản khác của Framework, tôi muốn thay thế nó bằng phiên bản của riêng tôi.

Có một trường hợp khác mà tôi đã gặp phải khi tôi muốn ghi đè một phương thức lớp bằng một phương thức mở rộng, vì vậy nó không chỉ dành riêng cho lớp chuỗi hoặc phương thức GetHashCode.

Tôi biết tôi có thể làm điều này với phân lớp con khỏi một lớp hiện có nhưng sẽ rất hữu ích nếu có thể làm điều đó với một phần mở rộng trong nhiều trường hợp.


Vì Từ điển là một cấu trúc dữ liệu trong bộ nhớ, nó có gì khác biệt nếu thuật toán băm thay đổi từ phiên bản này sang phiên bản tiếp theo? Nếu phiên bản khung thay đổi, thì rõ ràng ứng dụng đã được khởi động lại và từ điển đã được xây dựng lại.
David Nelson

Câu trả lời:


91

Không; một phương thức mở rộng không bao giờ được ưu tiên hơn một phương thức thể hiện có chữ ký phù hợp và không bao giờ tham gia vào tính đa hình ( GetHashCodelà một virtualphương thức).


"... không bao giờ tham gia vào tính đa hình (GetHashCode là một phương thức ảo)" Bạn có thể giải thích theo cách khác tại sao một phương thức mở rộng sẽ không bao giờ tham gia vào tính đa hình do nó là ảo? Tôi đang gặp sự cố khi thấy kết nối với hai câu lệnh đó.
Alex,

3
@Alex bằng cách đề cập đến ảo, tôi chỉ đơn giản là làm rõ ý nghĩa của việc đa hình. Trong hầu như tất cả các cách sử dụng GetHashCode, loại cụ thể là không xác định - vì vậy tính đa hình đang được sử dụng. Như vậy, các phương thức mở rộng sẽ không hữu ích ngay cả khi chúng được ưu tiên trong trình biên dịch thông thường. Những gì OP thực sự muốn là khỉ vá. Mà c # và .net không tạo điều kiện.
Marc Gravell

4
Điều đó thật đáng buồn, trong C # rất nhiều phương thức không có nghĩa gây hiểu lầm, ví dụ như .ToString()trong mảng. Nó chắc chắn là một tính năng cần thiết.
Hi-Angel

Tại sao bạn không gặp lỗi trình biên dịch sau đó? Vd: tôi gõ. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } }
LowLevel,

@LowLevel tại sao bạn sẽ?
Marc Gravell

7

Nếu phương thức có một chữ ký khác, thì nó có thể được thực hiện - vì vậy trong trường hợp của bạn: không.

Nhưng nếu không, bạn cần phải sử dụng kế thừa để thực hiện những gì bạn đang tìm kiếm.


2

Theo như tôi biết thì câu trả lời là không, bởi vì một phương thức mở rộng không phải là một thể hiện, đối với tôi nó giống như một phương thức intellisense cho phép bạn gọi một phương thức tĩnh bằng cách sử dụng một thể hiện của một lớp. Tôi nghĩ rằng một giải pháp cho vấn đề của bạn có thể là một trình đánh chặn chặn việc thực thi một phương thức cụ thể (ví dụ: GetHashCode ()) và làm điều gì đó khác. nhà máy đối tượng (hoặc vùng chứa IoC trong Castle) để các giao diện của chúng có thể bị chặn thông qua proxy động được tạo trong thời gian chạy. (Caslte cũng cho phép bạn chặn các thành viên ảo của các lớp)


0

Tôi đã tìm ra cách để gọi một phương thức mở rộng có cùng chữ ký với một phương thức lớp, tuy nhiên nó có vẻ không được thanh lịch cho lắm. Khi thử nghiệm với các phương pháp mở rộng, tôi nhận thấy một số hành vi không có giấy tờ. Mã mẫu:

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

Điều tôi nhận thấy là nếu tôi gọi phương thức thứ hai từ bên trong một phương thức mở rộng, nó sẽ gọi phương thức mở rộng phù hợp với chữ ký ngay cả khi có một phương thức lớp cũng khớp với chữ ký. Ví dụ trong đoạn mã trên, khi tôi gọi ExtFunc (), lần lượt gọi ele.GetDesc (), tôi nhận được chuỗi trả về "Extension GetDesc" thay vì chuỗi "Base GetDesc" mà chúng tôi mong đợi.

Kiểm tra mã:

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

Điều này cho phép bạn chuyển qua lại giữa các phương thức lớp và phương thức mở rộng theo ý muốn, nhưng yêu cầu bổ sung các phương thức "truyền qua" trùng lặp để đưa bạn vào "phạm vi" mà bạn mong muốn. Tôi gọi nó là phạm vi ở đây vì thiếu một từ tốt hơn. Hy vọng rằng ai đó có thể cho tôi biết nó thực sự được gọi là gì.

Bạn có thể đoán được tên phương thức "truyền qua" của tôi. Tôi cũng đã đùa giỡn với ý tưởng chuyển các đại diện cho chúng với hy vọng rằng một hoặc hai phương thức có thể hoạt động như một phương thức truyền cho nhiều phương thức có cùng một chữ ký. Thật không may, nó đã không được như sau khi ủy nhiệm được giải nén, nó luôn chọn phương thức lớp thay vì phương thức mở rộng ngay cả từ bên trong một phương thức mở rộng khác. "Phạm vi" không còn quan trọng nữa. Mặc dù vậy, tôi đã không sử dụng các đại diện Action và Func cho lắm nên có lẽ ai đó có kinh nghiệm hơn có thể hiểu được điều đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.