Câu hỏi Tại sao chúng ta không thể thay đổi công cụ sửa đổi truy cập trong khi ghi đè các phương thức trong C #?


Trong C #, chúng ta không thể thay đổi công cụ sửa đổi truy cập trong khi ghi đè một phương thức từ lớp cơ sở. ví dụ.

Class Base
{
   **protected** string foo()
   {
       return "Base";
   }
}

Class Derived : Base
{
   **public** override string foo()
   {
       return "Derived";
   }
}

Điều này không hợp lệ trong C #, Nó sẽ cung cấp lỗi thời gian biên dịch.

Tôi muốn biết lý do, tại sao nó không được phép. Có bất kỳ vấn đề kỹ thuật hoặc nó có thể dẫn đến một cái gì đó mà không nhất quán về hạn chế truy cập?


35
2018-06-04 13:08


gốc




Các câu trả lời:


Thay đổi công cụ sửa đổi truy cập của một phương thức trong một kiểu dẫn xuất là vô nghĩa đó là lý do tại sao nó không được phép:

Trường hợp 1: Ghi đè với quyền truy cập hạn chế hơn

Trường hợp này rõ ràng là không được phép do tình huống sau:

class Base
{
    public virtual void A() {}
}

class Derived: Base
{
    protected override void A()
}

Bây giờ chúng ta có thể nói:

List<Base> list;
list.Add(new Derived());
list[0].A() //Runtime access exception

Trường hợp 2: Ghi đè bằng công cụ sửa đổi truy cập ít hạn chế hơn

Điểm là gì? Ẩn phương thức và bạn đã hoàn thành. Rõ ràng nếu ai đó gọi qua kiểu cơ sở thì họ sẽ không có quyền truy cập vào phương thức mới được định nghĩa trong kiểu dẫn xuất nhưng điều đó phù hợp với cách tác giả của loại cơ sở muốn mọi thứ được như vậy bạn không có "quyền" để thay đổi điều đó. Nếu bạn muốn các chi tiết cụ thể của cuộc gọi lớp dẫn xuất từ ​​lớp dẫn xuất, trong trường hợp này new phương pháp hoạt động hoàn toàn tốt đẹp.

CHỈNH SỬA: Trường hợp mở rộng 2

Những gì tôi đang cố gắng để nói trong trường hợp 2, là bạn đã có phương tiện để thay đổi khả năng truy cập của bất kỳ phương pháp (ảo hay không) nếu bạn muốn thay đổi khả năng tiếp cận.

Xem xét mã sau:

public class Base
{
    protected virtual string WhoAmI()
    {
        return "Base";
    }
}

public class Derived : Base
{
    public new virtual string WhoAmI()
    {
        return "Derived";
    }
}

public class AnotherDerived : Derived
{
    public override string WhoAmI()
    {
        return "AnotherDerived";
    }
}

Với new từ khóa bạn đã tạo một phương pháp ảo mới hiệu quả cho Derived lớp có cùng tên và chữ ký. Lưu ý rằng nó được phép khai báo new phương pháp virtual, vì vậy bất kỳ lớp học nào xuất phát từ Derived sẽ được phép ghi đè lên nó.

Những gì không được phép là nhờ ai đó làm như sau:

 Base newBaseObject = new Derived();
 newBaseObject.WhoAmI() //WhoAmI is not accessible.

Nhưng thực tế này không liên quan gì đến việc có thể ghi đè WhoAmI() hay không. Dù thế nào thì tình huống này không bao giờ có thể là vì Base không khai báo public  WhoAmI().

Vì vậy, trong lý thuyết C # ở đâu Derived.WhoAmI() có thể ghi đè Base.WhoAmI() không có lợi ích thực tế khi làm như vậy bởi vì bạn sẽ không bao giờ có thể gọi phương thức ảo từ lớp cơ sở anyways, vì vậy new tùy chọn đã đáp ứng yêu cầu của bạn.

Tôi hy vọng điều này làm cho nó rõ ràng hơn.


23
2018-06-04 13:52



Xin chào InBetween, Cảm ơn bạn đã trợ giúp. Tôi rất thuyết phục về trường hợp 1 u giải thích ở đây. Nhưng m vẫn còn nhầm lẫn cho trường hợp 2. Khi tôi ghi đè một phương pháp, Dù sao tôi đang ghi đè (ẩn) hành vi của phương pháp lớp cơ sở. Vì vậy, không có gì để thay đổi những thứ cơ bản. - Rumit Parakhiya
@coder: Xem chỉnh sửa cho câu trả lời của tôi cho lý do về Trường hợp 2. - InBetween
Cảm ơn vì sự giúp đỡ. Nó xóa bỏ sự nghi ngờ của tôi. - Rumit Parakhiya
Tôi không hiểu làm thế nào bạn nói "không có lợi ích thực tế khi làm như vậy bởi vì bạn sẽ không bao giờ có thể gọi phương thức ảo từ lớp cơ sở anyways". Nó là rất phổ biến cho một lớp cơ sở để xác định một protected virtual để cho phép lớp con thay đổi hành vi được trưng bày bằng cách thực hiện trong lớp cơ sở. Các câu hỏi liên quan đến trường hợp 2 là làm thế nào để cả hai 1. ghi đè protected vritual MemberName và 2. phơi bày một thành viên công cộng được gọi là MemberName. Nghe có vẻ như nó là không thể mà không thay thế các lớp con duy nhất với hai lớp con để từng bước override và sau đó new. - binki
@binki Tôi không theo bạn chút nào. Tôi đã nói những thành viên được bảo vệ và ghi đè họ ở đâu không hữu ích? OP hỏi tại sao khi ghi đè các phương thức như vậy bạn không được phép thay đổi công cụ sửa đổi truy cập và câu trả lời của tôi đề cập chính xác mối quan tâm đó: ghi đè phương thức được bảo vệ và thay đổi công cụ sửa đổi truy cập của nó giúp bạn trong chính xác những gì? Và nó tốt hơn thế nào Mới? - InBetween


OK, tôi đã tìm thấy một ghi chú nhỏ từ Eric Lippert trong tài liệu tham khảo C # chú thích:

Một phương thức ảo được ghi đè vẫn được coi là một phương thức của lớp đã giới thiệu nó. Các quy tắc về độ phân giải quá tải trong một số trường hợp thích các thành viên của các kiểu có nguồn gốc hơn ... ghi đè một phương thức không "di chuyển" trong đó phương thức đó thuộc về hệ thống phân cấp này.

Vì vậy, đây là một quy tắc có chủ ý để ngăn chặn các vấn đề 'lớp cơ sở giòn' và cung cấp phiên bản tốt hơn, tức là ít vấn đề khi một lớp cơ sở thay đổi.

Nhưng lưu ý rằng nó không liên quan gì tới bảo mật, kiểu an toàn hoặc trạng thái đối tượng.


7
2018-06-04 13:49



Tôi nghĩ rằng nó chỉ cần làm mà không có nó là vô nghĩa. xem câu trả lời của tôi cho lý luận. - InBetween
Tôi không thể hiểu được rằng phần thứ nhất của câu của bạn. - Henk Holterman
xin lỗi điện thoại di động tự động hoàn thành ... Điều tôi muốn nói là tôi thấy khả năng thay đổi quyền truy cập khi ghi đè vô nghĩa. - InBetween
Vì vậy, bạn đang nói rằng lỗi trình biên dịch bạn nhận được trong một lớp con khi khả năng hiển thị thay đổi thành viên của lớp cơ sở tránh vấn đề lớp cơ sở giòn bằng cách buộc nhà phát triển xem lại lớp con tại điểm đó. Điều đó thực sự có vẻ hữu ích. - binki


Nếu bạn thay đổi các công cụ sửa đổi hiển thị từ một công cụ sửa đổi hạn chế hơn thành một công cụ sửa đổi ít hạn chế, bạn cho phép các máy khách lớp truy cập vào các phương thức được chỉ định để sử dụng nội bộ. Về cơ bản bạn đã cung cấp một phương tiện để thay đổi trạng thái lớp có thể không an toàn.


3
2018-06-04 13:12



class Derived { public string foo2() { return foo(); } } vì vậy tôi không nghĩ rằng lập luận của bạn là đủ. - Henk Holterman
@ Henk: Khuôn mặt đó mà bạn có thể phá vỡ các công cụ sửa đổi mức hiển thị lớp không thay đổi thực tế là bạn đang cho phép truy cập vào các phương thức được chỉ định để sử dụng nội bộ có thể gây ra sự cố. Nó có thể là trường hợp đó không có vấn đề sẽ phát sinh, hoặc các công cụ có thể thổi lên. Nếu bạn thấy rằng việc gói một thành viên không phải công khai để làm cho nó có thể truy cập công cộng không gây ra vấn đề, thiết kế lớp có thể là sai. Điều này không có nghĩa là phá vỡ các công cụ sửa đổi hiển thị là một ý tưởng hay. - John Percival Hackworth
Tôi đang mong đợi một câu trả lời về mặt giao diện và thay thế. Một lớp dẫn xuất có thể luôn luôn thêm vào với giao diện công cộng, nó không thể giảm bớt nó. Và đối số về trạng thái đối tượng đơn giản là không giữ. - Henk Holterman
@ Henk: Tất nhiên một lớp dẫn xuất có thể thêm vào giao diện, nhưng đó không phải là câu hỏi. Câu hỏi đặt ra là tại sao việc thay đổi mức độ hiển thị của phương thức hiện tại sang mức độ dễ chấp nhận hơn thông qua việc ghi đè là không được phép. Câu hỏi không liên quan đến việc gói hoặc sử dụng một phương pháp phi công cộng. Ghi đè, như được hiển thị trong câu hỏi, sẽ không ngăn chặn sự thay thế lớp, cũng như sẽ không thêm một phương thức công khai bổ sung (trừ khi khách hàng kiểm tra siêu dữ liệu của phương thức). - John Percival Hackworth
Tôi nghĩ đó là câu hỏi. Nhưng hãy xem câu trả lời của tôi về 'lớp cơ sở giòn'. - Henk Holterman


Bạn có thể làm cho truy cập của lớp dẫn xuất ít hơn cơ sở, nhưng không nhiều hơn. Nếu không nó sẽ mâu thuẫn với định nghĩa của cơ sở và phơi bày các thành phần của nó ngoài những gì được dự định.


1
2018-06-04 13:11



Không, bạn không thể thay đổi acces ở tất cả khi ghi đè. Và làm thế nào nó sẽ mâu thuẫn với lớp cơ sở trong bất kỳ cách nào? Một lớp dẫn xuất luôn có giao diện riêng của nó. - Henk Holterman
nếu bạn có thể hạn chế quyền truy cập, bạn sẽ sửa đổi giao diện. - InBetween


Giảm khả năng hiển thị là không thể vì nếu Base.Member có thể nhìn thấy và Derived.Member không hiển thị, điều đó sẽ phá vỡ toàn bộ "Derived là một Base”Khái niệm trong OOP. Tuy nhiên, khả năng hiển thị tăng không được phép có lẽ bởi vì các nhà phát triển ngôn ngữ nghĩ rằng việc thay đổi khả năng hiển thị sẽ là một sai lầm phần lớn thời gian. Tuy nhiên, bạn luôn có thể sử dụng new từ khóa để ẩn thành viên lớp cơ sở bằng cách giới thiệu một thành viên có cùng tên nhưng một hành vi khác. Thành viên mới này thuộc về giao diện của loại có nguồn gốc, do đó, tất nhiên bạn vẫn có thể truy cập vào giao diện của loại cơ sở bằng cách truyền tới loại cơ sở đó. Tùy thuộc vào cách bạn viết lớp con, new thành viên có thể tăng hiệu quả khả năng hiển thị thuộc tính của lớp cơ sở — nhưng hãy nhớ rằng thuộc tính của lớp cơ sở vẫn có thể được truy cập trực tiếp (ví dụ: lớp con của lớp con của bạn có thể truyền this đến Base và bỏ qua tài sản của bạn).

Câu hỏi ở đây là làm thế nào để cả hai  override và new cùng một thành viên có tên (định danh) trong một lớp con. Điều đó rõ ràng là không thể. Ít nhất, tôi có thể nói qua thử nghiệm rằng public new override string foo(){return "";} không phải là cú pháp cho điều đó. Tuy nhiên, bạn có thể nhận được hiệu ứng tương tự bằng cách sử dụng hai lớp con:

using System;
class Base
{
    protected virtual string foo()
    {
        return "Base";
    }
    public void ExhibitSubclassDependentBehavior()
    {
        Console.WriteLine("Hi, I am {0} and {1}.", GetType(), foo());
    }
}

abstract class AbstractDerived : Base
{
    protected virtual string AbstractFoo()
    {
        return base.foo();
    }
    protected override string foo()
    {
        return AbstractFoo();
    }
}

class Derived : AbstractDerived
{
    protected override string AbstractFoo()
    {
        return "Deprived";
    }
    public new string foo()
    {
        return AbstractFoo();
    }
}

static class Program
{
    public static void Main(string[] args)
    {
        var b = new Base();
        var d = new Derived();
        Base derivedAsBase = d;
        Console.Write(nameof(b) + " -> "); b.ExhibitSubclassDependentBehavior(); // "b -> Hi, I am Base and Base."
        Console.WriteLine(nameof(d) + " -> " + d.foo()); // "d -> Deprived"
        Console.Write(nameof(derivedAsBase) + " -> "); derivedAsBase.ExhibitSubclassDependentBehavior(); // "derivedAsBase -> Hi, I am Derived and Deprived."
    }
}

Lớp con trung gian (AbstractDerived) sử dụng override và giới thiệu một thành viên mới, được đặt tên khác nhau mà lớp con và phân lớp con có thể tiếp tục override thành viên của lớp cơ sở khi họ thấy phù hợp. Phân lớp con (Derived) sử dụng new giới thiệu API mới. Vì bạn chỉ có thể sử dụng new hoặc là override chỉ với một số nhận dạng cụ thể Một lần cho mỗi cấp lớp con, bạn cần hai cấp lớp con để sử dụng hiệu quả cả hai trên cùng một số nhận dạng.

Vì vậy, theo cách nào đó, bạn có thể thay đổi khả năng hiển thị trong khi các phương pháp ghi đè — đó chỉ là một nỗi đau và không có cú pháp nào tôi biết để hoàn thành nó chỉ với một cấp độ thừa kế. Tuy nhiên, bạn có thể phải sử dụng một số mẹo như thế này tùy thuộc vào giao diện bạn đang cố triển khai và lớp cơ sở của bạn trông như thế nào. Tức là, điều này có thể hoặc không thể là những gì bạn thực sự muốn làm. Nhưng tôi vẫn tự hỏi tại sao C # không chỉ hỗ trợ điều này để bắt đầu. IOW, "câu trả lời" này chỉ là một biểu hiện lại câu hỏi của OP với một cách giải quyết ;-).


1
2018-03-20 18:35





Lý do là hiển nhiên. An ninh và tính toàn vẹn của các đối tượng.

Trong ví dụ cụ thể này, nếu các thực thể bên ngoài bắt đầu sửa đổi thuộc tính của đối tượng được bảo vệ theo lớp cơ sở. Mọi thứ sẽ đi haywire. Điều gì về mã máy khách được viết dựa trên lớp cơ sở mà tất cả / bất kỳ lớp dẫn xuất nào phải tuân theo.


0
2018-06-04 13:10



Nó có không có gì để làm với an ninh. Và mã mong đợi lớp cơ sở đơn giản sẽ không nhìn thấy phương thức có nguồn gốc công khai. - Henk Holterman
Thay đổi quyền truy cập của các phương thức overriden chỉ là vô nghĩa vì nó không thêm bất kỳ lợi ích nào cho ngôn ngữ (tăng truy cập) hoặc không được phép vì bạn sẽ phá vỡ hợp đồng lớp cơ sở (giới hạn truy cập). Nó không liên quan gì đến an ninh. Xem câu trả lời của tôi cho lý do. - InBetween


nếu nó có các công cụ sửa đổi truy cập khác nhau, bạn thực sự không thể xem xét nó cùng một phương thức nữa. loại gợi ý một vấn đề với thiết kế của mô hình.

một câu hỏi hay hơn sẽ là lý do tại sao bạn muốn thay đổi các công cụ sửa đổi truy cập?


0
2018-06-04 13:15