2010-11-16 21 views
7
protected static new void WhyIsThisValidCode() 
{ 
} 

Tại sao bạn được phép ghi đè phương pháp tĩnh? Không có gì nhưng lỗi có thể đến từ nó, nó không hoạt động như bạn nghĩ.Tại sao phương pháp tĩnh ghi đè được nêu trong C#

Thực hiện các lớp sau đây.

class BaseLogger 
{ 
    protected static string LogName { get { return null; } } 

    public static void Log(string message) { Logger.Log(message, LogName); } 
} 

class SpecificLogger : BaseLogger 
{ 
    protected static string LogName { get { return "Specific"; } } 
} 

này được alowed, và mã

SpecificLogger.Log("test"); 

được altso alowed, nhưng nó không làm những gì bạn sẽ nghĩ bằng cách nhìn vào mã.

nó gọi Logger.Log với LogName = null.

Vậy tại sao điều này được phép?

+11

Như những người khác đã nói, điều này không ghi đè - và nó * chính xác * như tôi mong đợi. Xin đừng cho rằng mọi người đều nghĩ giống như cách bạn làm. –

Trả lời

24

Từ khóa new không ghi đè phương thức. Thay vào đó, nó tạo ra một phương thức mới có cùng tên độc lập với bản gốc. Không thể ghi đè một phương thức tĩnh vì chúng không phải là ảo

+1

Đôi khi tôi bỏ lỡ các phương thức lớp ảo trong C#. Nhưng chúng có lẽ không hữu ích thường đủ để đảm bảo thêm chúng vào ngôn ngữ. – CodesInChaos

15

Bạn không ghi đè nó, bạn đang ẩn nó. Một phương thức bình thường sẽ thể hiện chính xác cùng một hành vi, do đó không có gì cụ thể cho các phương thức tĩnh ở đây.

Ẩn chỉ hữu ích trong một vài trường hợp. Cái tôi thường gặp nhất là làm cho kiểu trả về cụ thể hơn trong một lớp dẫn xuất. Nhưng tôi chưa bao giờ xảy ra với các phương pháp tĩnh.

Một khu vực nơi các hàm tĩnh có tên nhất định có thể hữu ích là nếu bạn sử dụng sự phản chiếu và muốn nhận thông tin về mỗi lớp bằng cách trả về từ một phương thức. Nhưng tất nhiên trong hầu hết các trường hợp, một thuộc tính phù hợp hơn.

Và nó không có khả năng để tạo ra lỗi từ mã của bạn tạo ra một trình biên dịch cảnh báo:

Warning da 'StaticHiding.SpecificLogger.LogName' thừa hưởng thành viên 'StaticHiding.BaseLogger.LogName'. Sử dụng từ khóa mới nếu dự định ẩn.

Và nếu bạn sử dụng new bạn nên biết mình đang làm gì.

2

Các phương thức và trường tĩnh không thuộc về các cá thể lớp mà là các định nghĩa lớp. Các phương thức tĩnh không phát một phần trong cơ chế gửi đi ảo và không phải là một phần của bảng phương thức ảo.

Chúng chỉ là phương pháp và trường trên lớp cụ thể đó. Có thể giống như các phương pháp và trường được "kế thừa" bởi vì bạn có thể làm SpecificLogger.Log(), nhưng đó chỉ là một cái gì đó để giữ cho bạn khỏi phải tham khảo các lớp cơ sở tất cả các thời gian.

Phương thức và trường tĩnh thực sự chỉ là các phương thức và trường toàn cầu, chỉ là loại OO.

0

Bạn không ghi đè thuộc tính trong lớp cơ sở, thay vào đó hãy ẩn nó. Thuộc tính thực tế được sử dụng trong thời gian chạy phụ thuộc vào giao diện bạn đang làm việc.Ví dụ sau minh họa:

SpecificLogger a = new SpecificLogger(); 
BaseLogger b = new SpecificLogger(); 

Console.Write(a.Log); // Specific 
Console.Write(b.Log); // null 

Trong mã của bạn, phương thức Đăng nhập thực sự làm việc với giao diện BaseLogger - vì phương thức Đăng nhập là một phần của lớp BaseLogger.

Phương pháp và thuộc tính tĩnh không thể bị ghi đè và khi bạn muốn ẩn thuộc tính, bạn nên sử dụng từ khóa new để biểu thị rằng bạn đang ẩn nội dung nào đó.

9

Những người khác đã chỉ ra rằng đây không phải là trọng, nhưng vẫn còn nguyên câu hỏi ban đầu của bạn: tại sao bạn có thể làm điều đó? (Nhưng câu hỏi thực sự là "tại sao bạn có thể ẩn các phương pháp tĩnh".)

Đó là một tính năng không thể tránh khỏi hỗ trợ phiên bản độc lập của thành phần có chứa các lớp cơ sở và các thành phần sử dụng các lớp cơ sở đó.

Ví dụ, hãy tưởng tượng rằng thành phần CompB chứa lớp cơ sở và một số thành phần khác của CompD chứa một lớp dẫn xuất. Trong phiên bản 1 của CompB, có thể không có bất kỳ tài sản nào được gọi là LogName. Tác giả của CompD quyết định thêm một thuộc tính tĩnh gọi là LogName. Điều quan trọng cần hiểu tại thời điểm này là tác giả của v1 CompD không có ý định thay thế hoặc ẩn bất kỳ tính năng nào của lớp cơ sở - không có thành viên nào được gọi là LogName trong lớp cơ sở khi họ viết mã đó.

Bây giờ hãy tưởng tượng rằng một phiên bản mới của thư viện CompB được phát hành. Trong phiên bản mới này, tác giả đã thêm thuộc tính LogName. Điều gì sẽ xảy ra trong CompD? Các tùy chọn xuất hiện được:

  1. CompD không còn hoạt động vì LOGNAME nó giới thiệu cuộc đụng độ với LOGNAME thêm vào CompB
  2. Bằng cách nào đó làm cho LOGNAME của CompD thay thế các cơ sở CompB LOGNAME. (Nó không thực sự rõ ràng như thế nào điều này có thể làm việc với statics. Bạn có thể dự tính này với không statics mặc dù.)
  3. Điều trị hai thành viên LogName là thành viên hoàn toàn khác nhau có cùng tên. (Trong thực tế, họ không - chúng được gọi là BaseLogger.LogName và SpecificLogger.LogName. Nhưng vì trong C# chúng ta không cần phải hội đủ điều kiện tên thành viên với lớp, có vẻ như chúng giống nhau.)

NET chọn để làm 3. (và nó mà với cả hai tĩnh và không tĩnh học Nếu bạn muốn hành vi 2 -. thay thế - với phi tĩnh, sau đó căn cứ phải là virtual lớp bắt nguồn phải đánh dấu phương thức là override để làm rõ rằng chúng cố ý ghi đè phương thức trong lớp cơ sở. C# sẽ không bao giờ làm phương thức của lớp dẫn xuất thay thế phương thức của lớp cơ sở trừ khi lớp dẫn xuất đã khai báo rõ ràng rằng đây là những gì chúng muốn.) Điều này có thể an toàn vì hai thành viên không liên quan ed - cơ sở LogName thậm chí không tồn tại ở điểm mà một trong những nguồn gốc đã được giới thiệu. Và điều này là thích hợp hơn để đơn giản phá vỡ vì phiên bản mới nhất của lớp cơ sở đã giới thiệu một thành viên mới.

Nếu không có tính năng này, sẽ không thể cho các phiên bản mới của Khuôn khổ .NET thêm thành viên mới vào các lớp cơ sở hiện tại mà không bị thay đổi đột ngột.

Bạn nói rằng hành vi không phải là những gì bạn mong đợi. Trên thực tế nó là chính xác những gì tôi mong đợi, và những gì bạn có thể muốn trong thực tế. BaseLogger không có ý tưởng rằng SpecificLogger đã giới thiệu thuộc tính LogName của riêng nó. (Không có cơ chế mà nó có thể bởi vì bạn không thể ghi đè lên các phương thức tĩnh.Và khi tác giả của SpecificLogger viết rằng tài sản LogName, hãy nhớ rằng họ đã viết chống lại v1 của BaseLogger mà không có một LogName, do đó, họ không có ý định rằng nó nên thay thế phương thức cơ bản hoặc. Vì cả lớp không muốn thay thế, việc thay thế rõ ràng sẽ là điều sai trái.

Kịch bản duy nhất mà bạn nên kết thúc trong tình huống này là vì hai lớp này nằm trong các thành phần khác nhau. (Rõ ràng là bạn có thể tạo ra một kịch bản khi chúng ở trong cùng một thành phần, nhưng tại sao bạn lại làm điều đó? Nếu bạn sở hữu cả hai đoạn mã và giải phóng chúng trong một thành phần duy nhất, nó sẽ trở nên điên rồ khi làm điều này.) Và vì vậy BaseLogger sẽ nhận được thuộc tính LogName của chính nó, đó là chính xác những gì sẽ xảy ra. Bạn có thể đã viết:

SpecificLogger.Log("test"); 

nhưng biên dịch C# thấy rằng SpecificLogger không cung cấp một phương pháp Log, vì vậy nó quay này vào:

BaseLogger.Log("test"); 

bởi vì đó là nơi mà các phương pháp Log được định nghĩa. Vì vậy, bất cứ khi nào bạn định nghĩa một phương thức trong một lớp dẫn xuất không cố gắng ghi đè lên một phương thức hiện có, trình biên dịch C# chỉ ra điều này trong siêu dữ liệu. Ví dụ: (Có một cài đặt "newslot" trong siêu dữ liệu của phương thức cho biết, phương pháp này có nghĩa là mới, không liên quan đến bất kỳ thứ gì trong lớp cơ sở.)

Nhưng điều này mang lại cho bạn sự cố nếu bạn muốn biên dịch lại CompD. Giả sử bạn có báo cáo lỗi do một số mã hoàn toàn không liên quan và bạn cần phải phát hành phiên bản CompD mới. Bạn biên dịch nó chống lại Verison mới của CompB. Nếu mã bạn đã viết không được phép, bạn sẽ không thực sự có khả năng - mã cũ đã được biên dịch sẽ hoạt động, nhưng bạn sẽ không thể biên dịch các phiên bản mới của mã đó, điều này sẽ hơi điên .

Và như vậy, để hỗ trợ kịch bản này (thẳng thắn hơi mơ hồ), chúng cho phép bạn thực hiện việc này. Họ tạo ra một cảnh báo để cho bạn biết rằng bạn đã có một cuộc đụng độ đặt tên ở đây. Bạn cần phải cung cấp từ khóa new để loại bỏ nó.

Đây là một trường hợp tối nghĩa, nhưng nếu bạn muốn hỗ trợ kế thừa qua các ranh giới thành phần, bạn cần điều này, nếu không, việc bổ sung thành viên mới hoặc được bảo vệ trên lớp cơ sở sẽ luôn thay đổi. Đó là lý do tại sao điều này là ở đây. Nhưng đó là thực tế xấu bao giờ dựa vào nó, do đó thực tế là bạn nhận được một cảnh báo trình biên dịch. Việc sử dụng từ khóa new để loại bỏ cảnh báo chỉ nên là một điểm dừng. Điểm mấu chốt là: tính năng này chỉ tồn tại vì một lý do duy nhất, và đó là giúp bạn thoát khỏi một lỗ hổng trong trường hợp phiên bản mới của một số lớp cơ sở đã thêm thành viên chưa từng tồn tại, và đụng độ với một thành viên đã có trong lớp dẫn xuất của bạn. Nếu đó không phải là tình huống bạn đang ở, không sử dụng tính năng này.

(Tôi nghĩ họ thực sự cần ban hành một lỗi chứ không phải là một cảnh báo khi bạn bỏ qua mới, để làm điều này rõ ràng hơn.)

0

cho tôi ngạc nhiên mã sau đây được phép và biên dịch mà không cần bất kỳ lỗi trên khung .net 4.5.1, VS 2013.

class A 
{ 
    public static void Foo() 
    { 
    } 
} 

class B : A 
{ 

} 

class Program 
{ 
    static void main(string[] args) 
    { 
     B.Foo(); 
    } 
} 
Các vấn đề liên quan