Tại sao chúng ta cần từ khóa mới và tại sao hành vi mặc định là ẩn và không ghi đè?


81

Tôi đang xem bài đăng trên blog này và có những câu hỏi sau:

  • Tại sao chúng ta cần newtừ khóa, nó chỉ để xác định rằng một phương thức lớp cơ sở đang bị ẩn. Ý tôi là, tại sao chúng ta cần nó? Nếu chúng ta không sử dụng overridetừ khóa, chúng ta có đang ẩn phương thức lớp cơ sở không?
  • Tại sao mặc định trong C # là ẩn và không ghi đè? Tại sao các nhà thiết kế đã thực hiện nó theo cách này?

6
Một câu hỏi thú vị hơn một chút (đối với tôi) là tại sao việc che giấu lại hợp pháp. Đó thường là một lỗi (do đó là cảnh báo của trình biên dịch), hiếm khi xảy ra và luôn luôn có vấn đề.
Konrad Rudolph

Câu trả lời:


127

Những câu hỏi hay. Hãy để tôi trình bày lại chúng.

Tại sao lại hợp pháp để ẩn một phương pháp với một phương pháp khác?

Hãy để tôi trả lời câu hỏi đó bằng một ví dụ. Bạn có một giao diện từ CLR v1:

interface IEnumerable
{
    IEnumerator GetEnumerator();
}

Siêu. Bây giờ trong CLR v2, bạn có generics và bạn nghĩ "anh bạn, giá như chúng ta có generics trong v1 thì tôi đã làm cho giao diện này trở thành một giao diện chung. Nhưng tôi đã không làm như vậy. Tôi nên tạo một cái gì đó tương thích với nó bây giờ chung chung để Tôi nhận được những lợi ích của generic mà không mất khả năng tương thích ngược với mã mà IEnumerable mong đợi. "

interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> .... uh oh

Bạn định gọi phương thức GetEnumerator là IEnumerable<T>gì? Hãy nhớ rằng, bạn muốn nó ẩn GetEnumerator trên giao diện cơ sở không chung chung. Bạn không bao giờ muốn thứ đó được gọi trừ khi bạn đang ở trong một tình huống tính toán ngược một cách rõ ràng.

Điều đó một mình biện minh cho phương pháp ẩn. Để biết thêm suy nghĩ về cách biện minh của phương pháp ẩn, hãy xem bài viết của tôi về chủ đề này .

Tại sao ẩn mà không có "mới" gây ra một cảnh báo?

Bởi vì chúng tôi muốn bạn biết rằng bạn đang che giấu điều gì đó và có thể vô tình làm điều đó. Hãy nhớ rằng, bạn có thể vô tình che giấu điều gì đó do việc chỉnh sửa lớp cơ sở do người khác thực hiện, chứ không phải do bạn chỉnh sửa lớp dẫn xuất của mình.

Tại sao ẩn mà không có "mới" là một cảnh báo hơn là một lỗi?

Cùng một lý do. Bạn có thể vô tình che giấu điều gì đó vì bạn vừa chọn một phiên bản mới của lớp cơ sở. Việc này xảy ra mọi lúc. FooCorp tạo ra một lớp cơ sở B. BarCorp tạo một lớp dẫn xuất D với một Thanh phương thức, bởi vì khách hàng của họ thích phương thức đó. FooCorp nhìn thấy điều đó và nói này, đó là một ý tưởng hay, chúng ta có thể đưa chức năng đó vào lớp cơ sở. Họ làm như vậy và gửi một phiên bản mới của Foo.DLL, và khi BarCorp chọn phiên bản mới, sẽ thật tuyệt nếu họ được thông báo rằng phương thức của họ hiện ẩn phương thức lớp cơ sở.

Chúng tôi muốn tình huống đó là một cảnh báo chứ không phải là một lỗi bởi vì biến nó thành một lỗi có nghĩa là đây là một dạng khác của vấn đề lớp cơ sở giòn . C # đã được thiết kế cẩn thận để khi ai đó thực hiện thay đổi đối với lớp cơ sở, các tác động lên mã sử dụng lớp dẫn xuất được giảm thiểu.

Tại sao ẩn và không ghi đè mặc định?

Vì ghi đè ảo rất nguy hiểm . Ghi đè ảo cho phép các lớp dẫn xuất thay đổi hành vi của mã đã được biên dịch để sử dụng các lớp cơ sở. Làm điều gì đó nguy hiểm như ghi đè phải là việc bạn làm một cách có ý thức và có chủ ý , không phải do ngẫu nhiên.


1
FWIW, tôi không đồng ý rằng "[t] hat một mình biện minh cho việc ẩn phương pháp." Đôi khi tốt hơn là phá vỡ khả năng tương thích ngược. Python 3 đã chỉ ra rằng điều này thực sự hoạt động mà không phá hủy quá nhiều công việc. Điều quan trọng cần lưu ý là “tính tương thích ngược bằng mọi giá” không phải là lựa chọn hợp lý duy nhất. Và lý do này chuyển sang điểm khác: thay đổi một lớp cơ bản sẽ luôn giòn. Phá vỡ một công trình phụ thuộc thực sự có thể thích hợp hơn. (Nhưng 1 cho việc trả lời các câu hỏi từ nhận xét của tôi bên dưới câu hỏi thực tế.)
Konrad Rudolph

@Konrad: Tôi đồng ý rằng khả năng tương thích ngược có thể gây hại. Mono đã quyết định bỏ hỗ trợ cho 1.1 trong các bản phát hành trong tương lai.
simendsjo

1
@Konrad: Phá vỡ khả năng tương thích ngược với ngôn ngữ của bạn rất khác so với việc khiến những người sử dụng ngôn ngữ của bạn dễ dàng phá vỡ khả năng tương thích ngược với mã của riêng họ. Ví dụ của Eric là trường hợp thứ hai. Và người viết mã đó đã đưa ra quyết định rằng anh ta muốn tương thích ngược. Ngôn ngữ sẽ hỗ trợ quyết định đó nếu có thể.
Brian

Xin Chúa trả lời, Eric. Tuy nhiên, có một lý do cụ thể hơn khiến việc ghi đè theo mặc định là không tốt. Nếu một lớp cơ sở Base giới thiệu một phương thức mới Duck () (trả về một đối tượng vịt) sẽ có cùng chữ ký với phương thức hiện có của bạn Duck () (tạo nên Mario duck) trong một lớp dẫn xuất Derived (tức là không có giao tiếp bạn và tác giả của Base), bạn không muốn khách hàng của cả hai lớp làm Mario vịt khi họ chỉ muốn một con Vịt.
jyoungdev

Tóm lại, nó chủ yếu liên quan đến việc thay đổi các lớp cơ sở và giao diện.
jyoungdev

15

Nếu phương thức trong lớp dẫn xuất được đặt trước từ khóa new, phương thức được định nghĩa là độc lập với phương thức trong lớp cơ sở

Tuy nhiên nếu bạn không chỉ định mới hoặc ghi đè, kết quả đầu ra giống như khi bạn chỉ định mới, nhưng bạn sẽ nhận được cảnh báo trình biên dịch (vì bạn có thể không biết rằng bạn đang ẩn một phương thức trong phương thức lớp cơ sở, hoặc thực sự bạn có thể đã muốn ghi đè nó và chỉ đơn thuần là quên bao gồm từ khóa).

Vì vậy, nó giúp bạn tránh những sai lầm và hiển thị rõ ràng những gì bạn muốn làm và nó làm cho mã dễ đọc hơn, vì vậy người ta có thể dễ dàng hiểu mã của bạn.


12

Cần lưu ý rằng tác dụng duy nhất của newtrong bối cảnh này là ngăn chặn Cảnh báo. Không có sự thay đổi về ngữ nghĩa.

Vì vậy, một câu trả lời là: Chúng ta cần newbáo hiệu cho trình biên dịch rằng việc ẩn là có chủ ý và để loại bỏ cảnh báo.

Câu hỏi tiếp theo là: Nếu bạn không / không thể ghi đè một phương thức, tại sao bạn lại giới thiệu một phương thức khác có cùng tên? Vì ẩn về bản chất là xung đột tên tuổi. Và tất nhiên bạn sẽ tránh nó trong hầu hết các trường hợp.

Lý do chính đáng duy nhất tôi có thể nghĩ ra cho việc cố tình ẩn là khi một cái tên bị buộc vào bạn bởi một giao diện.


6

Trong C #, các thành viên được niêm phong theo mặc định có nghĩa là bạn không thể ghi đè chúng (trừ khi được đánh dấu bằng virtualhoặc abstracttừ khóa) và điều này vì lý do hiệu suất . Công cụ sửa đổi mới được sử dụng để ẩn rõ ràng một thành viên được kế thừa.


4
Nhưng khi nào bạn thực sự muốn sử dụng công cụ sửa đổi mới?
simendsjo

1
Khi bạn muốn ẩn một cách rõ ràng một thành viên được kế thừa từ một lớp cơ sở. Ẩn một thành viên kế thừa có nghĩa là phiên bản dẫn xuất của thành viên đó sẽ thay thế phiên bản lớp cơ sở.
Darin Dimitrov

3
Nhưng bất kỳ phương thức nào sử dụng lớp cơ sở sẽ vẫn gọi việc triển khai cơ sở, không phải là dẫn xuất. Đây dường như là một tình huống nguy hiểm đối với tôi.
simendsjo

@simendsjo, @Darin Dimitrov: Tôi cũng gặp khó khăn khi nghĩ về trường hợp sử dụng mà từ khóa mới thực sự cần thiết. Đối với tôi, dường như luôn có những cách tốt hơn. Tôi cũng tự hỏi liệu việc ẩn triển khai lớp cơ sở có phải là một lỗ hổng thiết kế dễ dẫn đến lỗi hay không.
Dirk Vollmar

2

Nếu việc ghi đè là mặc định mà không chỉ định overridetừ khóa, bạn có thể vô tình ghi đè một số phương thức cơ sở của mình chỉ do sự bình đẳng về tên.

Chiến lược trình biên dịch .Net là đưa ra cảnh báo nếu có điều gì đó xảy ra sai, chỉ để an toàn, vì vậy trong trường hợp này nếu ghi đè là mặc định, sẽ phải có cảnh báo cho mỗi phương thức ghi đè - đại loại như 'cảnh báo: kiểm tra xem bạn có thực sự muốn không để ghi đè '.


0

Tôi đoán chủ yếu sẽ là do kế thừa nhiều giao diện. Sử dụng các giao diện kín đáo, rất có thể hai giao diện khác nhau sử dụng cùng một chữ ký phương pháp. Cho phép sử dụng newtừ khóa sẽ cho phép bạn tạo các triển khai khác nhau này với một lớp, thay vì phải tạo hai lớp riêng biệt.

Đã cập nhật ... Eric đã cho tôi ý tưởng về cách cải thiện ví dụ này.

public interface IAction1
{
    int DoWork();
}
public interface IAction2
{
    string DoWork();
}

public class MyBase : IAction1
{
    public int DoWork() { return 0; }
}
public class MyClass : MyBase, IAction2
{
    public new string DoWork() { return "Hi"; }
}

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        var ret0 = myClass.DoWork(); //Hi
        var ret1 = ((IAction1)myClass).DoWork(); //0
        var ret2 = ((IAction2)myClass).DoWork(); //Hi
        var ret3 = ((MyBase)myClass).DoWork(); //0
        var ret4 = ((MyClass)myClass).DoWork(); //Hi
    }
}

1
Khi sử dụng nhiều giao diện, bạn có thể đặt tên cho các phương thức giao diện một cách rõ ràng để tránh cộng gộp. Tôi không hiểu những gì bạn có ý nghĩa bởi "thực hiện khác nhau cho một lớp" cả hai đều có cùng một chữ ký ...
simendsjo

Các newtừ khóa cho phép bạn cũng thay đổi cơ sở thừa kế cho tất cả các trẻ em từ thời điểm đó.
Matthew Whited

-1

Như đã lưu ý, phương thức / thuộc tính ẩn làm cho nó có thể thay đổi mọi thứ về một phương thức hoặc thuộc tính mà không thể dễ dàng thay đổi nếu không. Một tình huống mà điều này có thể hữu ích là cho phép một lớp kế thừa có các thuộc tính đọc-ghi mà chỉ đọc trong lớp cơ sở. Ví dụ: giả sử một lớp cơ sở có một loạt các thuộc tính chỉ đọc được gọi là Value1-Value40 (tất nhiên, một lớp thực sẽ sử dụng tên tốt hơn). Một hậu duệ được niêm phong của lớp này có một hàm tạo lấy một đối tượng của lớp cơ sở và sao chép các giá trị từ đó; lớp không cho phép thay đổi chúng sau đó. Một phần tử con khác, có thể kế thừa, khai báo các thuộc tính đọc-ghi được gọi là Value1-Value40, khi đọc, hoạt động giống như các phiên bản lớp cơ sở nhưng khi được viết cho phép các giá trị được ghi.

Một điều khó chịu với cách tiếp cận này - có lẽ ai đó có thể giúp tôi - là tôi không biết cách nào để vừa phủ bóng vừa ghi đè một thuộc tính cụ thể trong cùng một lớp. Có bất kỳ ngôn ngữ CLR nào cho phép điều đó không (tôi sử dụng vb 2005)? Sẽ rất hữu ích nếu đối tượng lớp cơ sở và các thuộc tính của nó có thể là trừu tượng, nhưng điều đó sẽ yêu cầu một lớp trung gian ghi đè các thuộc tính Value1 thành Value40 trước khi một lớp con có thể phủ bóng chúng.

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.