Tại sao không thể ghi đè một thuộc tính chỉ có getter và thêm một setter? [đóng cửa]


141

Tại sao mã C # sau không được phép:

public abstract class BaseClass
{
    public abstract int Bar { get;}
}

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get { return 0; }
        set {}
    }
}

CS0546 'ConcreteClass.Bar.set': không thể ghi đè vì 'BaseClass.Bar' không có bộ truy cập tập hợp quá mức


9
StackOverflow đã giới hạn khả năng trả lời các câu hỏi thay mặt cho Microsoft. Hãy xem xét lại câu hỏi của bạn.
Jay Bazuzi

1
Ngẫu nhiên, một biện pháp đơn giản cho hành vi gây phiền nhiễu này trong .net là có một thuộc tính chỉ đọc không ảo Foo, không có gì ngoài việc bọc một GetFoophương thức trừu tượng được bảo vệ . Một kiểu dẫn xuất trừu tượng có thể tạo bóng cho thuộc tính bằng một thuộc tính đọc-ghi không ảo mà không làm gì ngoài việc bao bọc GetFoophương thức đã nói ở trên cùng với một SetFoophương thức trừu tượng ; một loại dẫn xuất cụ thể có thể làm như trên nhưng cung cấp định nghĩa cho GetFooSetFoo.
supercat

1
Chỉ cần thêm dầu vào lửa, C ++ / CLI cho phép điều này.
ymett

1
Khả năng thêm một người truy cập khi ghi đè một thuộc tính đã được đề xuất như là một thay đổi cho phiên bản tương lai của C # ( github.com/dotnet/roslyn/issues/9482 ).
Trái phiếu Brandon

1
Thành thật mà nói, câu hỏi này thường được đặt ra khi cần mở rộng một loại thư viện hiện có sử dụng mẫu sau: lớp cơ sở chỉ đọc, lớp có thể thay đổi được dẫn xuất. Bởi vì thư viện đã sử dụng một mẫu xấu (như WPF), nên cần phải giải quyết mẫu xấu này. Bài giảng không cứu người ta khỏi phải thực hiện một cách giải quyết vào cuối ngày.
rwong

Câu trả lời:


-4

Bởi vì người viết của Baseclass đã tuyên bố rõ ràng rằng Bar phải là một thuộc tính chỉ đọc. Nó không có ý nghĩa cho các phái sinh để phá vỡ hợp đồng này và làm cho nó đọc-viết.

Tôi với Microsoft về điều này.
Giả sử tôi là một lập trình viên mới, người được yêu cầu viết mã chống lại đạo hàm Baseclass. tôi viết một cái gì đó giả định rằng Bar không thể được ghi vào (vì Baseclass tuyên bố rõ ràng rằng đó là thuộc tính chỉ nhận được). Bây giờ với đạo hàm của bạn, mã của tôi có thể bị hỏng. ví dụ

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

  public void setSource(BaseClass b)
  {
    _source = b;
    _currentBar = b.Bar;
  }

  public Bar getBar()
  { return _currentBar;  }
}

Vì Bar không thể được đặt theo giao diện BaseClass, BarProvider cho rằng bộ nhớ đệm là điều an toàn để thực hiện - Vì Bar không thể được sửa đổi. Nhưng nếu được thiết lập trong một dẫn xuất, lớp này có thể phục vụ các giá trị cũ nếu ai đó sửa đổi thuộc tính Bar của đối tượng _source bên ngoài. Quan điểm là ' Hãy cởi mở, tránh làm những điều lén lút và làm mọi người ngạc nhiên '

Cập nhật : Ilya Ryzhenkov hỏi 'Tại sao các giao diện không chơi theo cùng một quy tắc?' Hmm .. điều này trở nên lầy lội hơn khi tôi nghĩ về nó.
Giao diện là một hợp đồng có nội dung 'mong muốn việc triển khai có một thuộc tính đọc có tên là Bar.' Cá nhân tôi ít có khả năng đưa ra giả định đó chỉ đọc nếu tôi thấy Giao diện. Khi tôi thấy một thuộc tính chỉ nhận được trên một giao diện, tôi đọc nó là 'Bất kỳ triển khai nào cũng sẽ hiển thị thanh thuộc tính này' ... trên lớp cơ sở, nó nhấp vào 'Thanh là thuộc tính chỉ đọc'. Tất nhiên về mặt kỹ thuật bạn sẽ không phá vỡ hợp đồng .. bạn đang làm nhiều hơn. Vì vậy, bạn hiểu đúng .. Tôi gần gũi bằng cách nói 'làm cho khó hiểu nhất có thể để hiểu lầm'.


94
Tôi vẫn chưa bị thuyết phục. Ngay cả khi không có trình thiết lập rõ ràng, điều này không đảm bảo rằng thuộc tính sẽ luôn trả về cùng một đối tượng - nó vẫn có thể được thay đổi bằng một số phương thức khác.
ripper234

34
Không nên giống nhau cho các giao diện, sau đó? Bạn có thể có thuộc tính chỉ đọc trên giao diện và đọc / ghi trên kiểu triển khai.
Ilya Ryzhenkov

49
Tôi không đồng ý: lớp cơ sở chỉ khai báo thuộc tính là không có setter; đó không giống như tài sản chỉ đọc. "Chỉ đọc" chỉ có nghĩa là bạn gán cho nó. Không có đảm bảo có liên quan nào được tích hợp vào C # cả. Bạn có thể có một thuộc tính chỉ nhận được thay đổi mỗi lần hoặc thuộc tính get / set nơi trình cài đặt ném một InvalidOperationException("This instance is read-only").
Roman Starkov

21
Nhìn vào getters và setters như các phương thức, tôi không thấy lý do tại sao nó nên là "vi phạm hợp đồng" hơn là thêm một phương thức mới. Hợp đồng cơ sở hạng không có gì để làm với những gì chức năng là không có mặt, chỉ với những gì đang có mặt.
Dave Cousineau

37
-1 Tôi không đồng ý với câu trả lời này và thấy nó sai. Vì một lớp cơ sở xác định hành vi mà bất kỳ lớp nào cũng phải tuân theo (Xem Nguyên tắc thay thế của Liskov), nhưng không (và không nên) hạn chế thêm hành vi. Một lớp cơ sở chỉ có thể định nghĩa hành vi phải là gì và nó không thể (và không nên) chỉ định hành vi 'không được' (nghĩa là 'không được ghi ở mức cụ thể)'.
Marcel Valdez Orozco

39

Tôi nghĩ lý do chính đơn giản là cú pháp quá rõ ràng để làm việc này theo bất kỳ cách nào khác. Mã này:

public override int MyProperty { get { ... } set { ... } }

là khá rõ ràng rằng cả getvà và setđược ghi đè. Không cóset trong lớp cơ sở, vì vậy trình biên dịch phàn nàn. Giống như bạn không thể ghi đè một phương thức không được xác định trong lớp cơ sở, bạn cũng không thể ghi đè một trình thiết lập.

Bạn có thể nói rằng trình biên dịch nên đoán ý định của bạn và chỉ áp dụng ghi đè cho phương thức có thể bị ghi đè (tức là getter trong trường hợp này), nhưng điều này đi ngược lại một trong các nguyên tắc thiết kế C # - rằng trình biên dịch không được đoán ý định của bạn , bởi vì nó có thể đoán sai mà bạn không biết.

Tôi nghĩ rằng cú pháp sau đây có thể làm tốt, nhưng như Eric Lippert vẫn nói, thực hiện ngay cả một tính năng nhỏ như thế này vẫn là một nỗ lực lớn ...

public int MyProperty
{
    override get { ... }
    set { ... }
}

hoặc, đối với các thuộc tính tự động thực hiện,

public int MyProperty { override get; set; }

19

Tôi đã vấp phải cùng một vấn đề ngày hôm nay và tôi nghĩ rằng có một lý do rất hợp lệ cho việc này muốn.

Trước tiên tôi muốn tranh luận rằng việc có một tài sản chỉ nhận không nhất thiết phải chuyển thành chỉ đọc. Tôi hiểu nó là "Từ giao diện này / rút ngắn bạn có thể nhận được giá trị này", điều đó không có nghĩa là việc thực hiện giao diện / lớp trừu tượng đó sẽ không cần người dùng / chương trình đặt giá trị này một cách rõ ràng. Các lớp trừu tượng phục vụ mục đích thực hiện một phần chức năng cần thiết. Tôi thấy hoàn toàn không có lý do tại sao một lớp kế thừa không thể thêm một setter mà không vi phạm bất kỳ hợp đồng nào.

Sau đây là một ví dụ đơn giản về những gì tôi cần ngày hôm nay. Cuối cùng tôi đã phải thêm một setter trong giao diện của mình chỉ để khắc phục điều này. Lý do cho việc thêm setter và không thêm, giả sử, một phương thức SetProp là vì một triển khai cụ thể của giao diện được sử dụng DataContract / DataMember để tuần tự hóa Prop, sẽ rất phức tạp nếu tôi phải thêm một thuộc tính khác chỉ nhằm mục đích của tuần tự hóa.

interface ITest
{
    // Other stuff
    string Prop { get; }
}

// Implements other stuff
abstract class ATest : ITest
{
    abstract public string Prop { get; }
}

// This implementation of ITest needs the user to set the value of Prop
class BTest : ATest
{
    string foo = "BTest";
    public override string Prop
    {
        get { return foo; }
        set { foo = value; } // Not allowed. 'BTest.Prop.set': cannot override because 'ATest.Prop' does not have an overridable set accessor
    }
}

// This implementation of ITest generates the value for Prop itself
class CTest : ATest
{
    string foo = "CTest";
    public override string Prop
    {
        get { return foo; }
        // set; // Not needed
    }
}

Tôi biết đây chỉ là một bài đăng "2 xu của tôi", nhưng tôi cảm thấy với poster gốc và cố gắng hợp lý hóa rằng đây là một điều tốt đối với tôi, đặc biệt khi xem xét rằng những hạn chế tương tự không áp dụng khi kế thừa trực tiếp từ một giao diện.

Ngoài ra, việc đề cập đến việc sử dụng mới thay vì ghi đè không áp dụng ở đây, đơn giản là nó không hoạt động và ngay cả khi nó không mang lại cho bạn kết quả mong muốn, cụ thể là một getter ảo như được mô tả bởi giao diện.


2
Hoặc, trong trường hợp của tôi, {get; thiết lập riêng tư; }. Tôi không vi phạm bất kỳ hợp đồng nào vì bộ này vẫn ở chế độ riêng tư và chỉ liên quan đến lớp phái sinh.
Machtyn

16

Có thể

tl; dr - Bạn có thể ghi đè phương thức chỉ lấy bằng setter nếu muốn. Về cơ bản chỉ là:

  1. Tạo một thuộc newtính có cả a getvà aset sử dụng cùng tên.

  2. Nếu bạn không làm gì khác, thì cũ get phương thức sẽ vẫn được gọi khi lớp dẫn xuất được gọi thông qua kiểu cơ sở của nó. Để khắc phục điều này, hãy thêm một abstractlớp trung gian sử dụng phương thức overridegetđể buộc nó trả về getkết quả của phương thức mới .

Điều này cho phép chúng tôi ghi đè các thuộc tính với get/set ngay cả khi chúng thiếu một trong định nghĩa cơ sở của chúng.

Là một phần thưởng, bạn cũng có thể thay đổi loại trả lại nếu bạn muốn.

  • Nếu định nghĩa cơ sở là get-only, thì bạn có thể sử dụng loại trả về có nguồn gốc nhiều hơn.

  • Nếu định nghĩa cơ sở là set-only, thì bạn có thể cho chúng tôi loại trả về ít dẫn xuất hơn.

  • Nếu định nghĩa cơ sở đã get/set , thì:

    • bạn có thể sử dụng loại trả về có nguồn gốc nhiều hơn nếu bạn thực hiệnset ;

    • bạn có thể sử dụng loại trả về có nguồn gốc ít hơn nếu bạn thực hiện nó một lần get.

Trong mọi trường hợp, bạn có thể giữ cùng kiểu trả về nếu muốn. Các ví dụ dưới đây sử dụng cùng loại trả về cho đơn giản.

Tình huống: Có từ trước get Tài sản

Bạn có một số cấu trúc lớp mà bạn không thể sửa đổi. Có thể đó chỉ là một lớp hoặc là cây thừa kế có sẵn. Dù thế nào đi nữa, bạn muốn thêm một setphương thức vào một thuộc tính, nhưng không thể.

public abstract class A                     // Pre-existing class; can't modify
{
    public abstract int X { get; }          // You want a setter, but can't add it.
}
public class B : A                          // Pre-existing class; can't modify
{
    public override int X { get { return 0; } }
}

Vấn đề: Không thể overridecác get-only với get/set

Bạn muốn overridevới một get/ setproperty, nhưng nó sẽ không được biên dịch.

public class C : B
{
    private int _x;
    public override int X
    {
        get { return _x; }
        set { _x = value; }   //  Won't compile
    }
}

Giải pháp: Sử dụng một abstractlớp trung gian

Trong khi bạn không thể trực tiếp overridevới get/ settài sản, bạn có thể :

  1. Tạo một new get/ thuộc settính có cùng tên.

  2. overridegetphương pháp cũ với một người truy cập vào getphương thức mới để đảm bảo tính nhất quán.

Vì vậy, đầu tiên bạn viết abstractlớp trung gian:

public abstract class C : B
{
    //  Seal off the old getter.  From now on, its only job
    //  is to alias the new getter in the base classes.
    public sealed override int X { get { return this.XGetter; }  }
    protected abstract int XGetter { get; }
}

Sau đó, bạn viết lớp không biên dịch trước đó. Nó sẽ biên dịch lần này vì bạn không thực sự overridegettài sản chung; thay vào đó, bạn đang thay thế nó bằng newtừ khóa.

public class D : C
{
    private int _x;
    public new virtual int X { get { return this._x; } set { this._x = value; } }

    //  Ensure base classes (A,B,C) use the new get method.
    protected sealed override int XGetter { get { return this.X; } }
}

Kết quả: Mọi thứ đều hoạt động!

Rõ ràng, điều này làm việc như dự định cho D.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

test.X = 7;
Print(test.X);      // Prints "7", as intended.

Mọi thứ vẫn hoạt động như dự định khi xem Dnhư một trong các lớp cơ sở của nó, ví dụ Ahoặc B. Nhưng, lý do tại sao nó hoạt động có thể là một chút ít rõ ràng.

var test = new D() as B;
//test.X = 7;       // This won't compile, because test looks like a B,
                    // and B still doesn't provide a visible setter.

Tuy nhiên, định nghĩa của lớp cơ sở getcuối cùng vẫn bị ghi đè bởi định nghĩa của lớp dẫn xuất get, vì vậy nó vẫn hoàn toàn nhất quán.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

var baseTest = test as A;
Print(test.X);      // Prints "7", as intended.

Thảo luận

Phương pháp này cho phép bạn thêm setcác phương thức vào getcác thuộc tính -only. Bạn cũng có thể sử dụng nó để làm những việc như:

  1. Thay đổi bất kỳ thuộc tính nào thành thuộc tính get-only, set-only hoặc get-and- set, bất kể nó thuộc lớp cơ sở nào.

  2. Thay đổi kiểu trả về của một phương thức trong các lớp dẫn xuất.

Những nhược điểm chính là có nhiều mã hóa hơn để làm và thêm abstract classvào trong cây thừa kế. Điều này có thể gây một chút khó chịu với các nhà xây dựng lấy tham số vì chúng phải được sao chép / dán trong lớp trung gian.


Đưa ra câu hỏi của riêng mình: stackoverflow.com/questions/22210971
Nat

Cách tiếp cận tốt đẹp (+1). Tuy nhiên, tôi nghi ngờ liệu điều này sẽ hoạt động trơn tru với khung thực thể.
Mike de Klerk

9

Tôi đồng ý rằng việc không thể ghi đè một getter trong loại dẫn xuất là một kiểu chống. Chỉ đọc chỉ định thiếu thực hiện, không phải là hợp đồng của một chức năng thuần túy (ngụ ý câu trả lời bỏ phiếu hàng đầu).

Tôi nghi ngờ Microsoft có giới hạn này hoặc vì cùng một quan niệm sai lầm đã được thúc đẩy, hoặc có lẽ vì đơn giản hóa ngữ pháp; mặc dù, bây giờ phạm vi đó có thể được áp dụng để nhận hoặc đặt riêng lẻ, có lẽ chúng ta có thể hy vọng ghi đè cũng có thể.

Quan niệm sai lầm được chỉ ra bởi câu trả lời bỏ phiếu hàng đầu, rằng một tài sản chỉ đọc bằng cách nào đó nên "thuần túy" hơn một tài sản đọc / ghi là vô lý. Đơn giản chỉ cần nhìn vào nhiều thuộc tính chỉ đọc phổ biến trong khung; giá trị không phải là một hằng số / hoàn toàn chức năng; ví dụ, DateTime. Bây giờ chỉ đọc, nhưng bất cứ thứ gì ngoại trừ một giá trị chức năng thuần túy. Việc cố gắng 'lưu trữ' một giá trị của một thuộc tính chỉ đọc giả sử rằng nó sẽ trả về cùng một giá trị vào lần tới là rủi ro.

Trong mọi trường hợp, tôi đã sử dụng một trong những chiến lược sau để khắc phục giới hạn này; cả hai đều kém hoàn hảo, nhưng sẽ cho phép bạn khập khiễng vượt qua sự thiếu hụt ngôn ngữ này:

   class BaseType
   {
      public virtual T LastRequest { get {...} }
   }

   class DerivedTypeStrategy1
   {
      /// get or set the value returned by the LastRequest property.
      public bool T LastRequestValue { get; set; }

      public override T LastRequest { get { return LastRequestValue; } }
   }

   class DerivedTypeStrategy2
   {
      /// set the value returned by the LastRequest property.
      public bool SetLastRequest( T value ) { this._x = value; }

      public override T LastRequest { get { return _x; } }

      private bool _x;
   }

DateTime. Hiện tại hoàn toàn là chức năng, ở chỗ nó không có tác dụng phụ (thực tế là cần có thời gian để thực thi có thể được gọi là hiệu ứng phụ, nhưng vì thời gian thực hiện - ít nhất là khi bị giới hạn và ngắn - không được xem xét một tác dụng phụ của bất kỳ phương pháp nào khác, tôi cũng sẽ không coi nó là một phương pháp ở đây).
supercat

1
Chào siêu xe. Bạn đã đúng về các tác dụng phụ có thể quan sát được bên ngoài ngăn chặn một chức năng không thuần túy, nhưng hạn chế khác là giá trị kết quả của hàm thuần túy chỉ phải phụ thuộc vào các tham số. Do đó, một thuộc tính tĩnh thuần túy, như là một hàm, phải là bất biến và không đổi. Một trình biên dịch tinh vi có thể thay thế tất cả các cuộc gọi tiếp theo thành một hàm thuần túy không có tham số nào với kết quả được trả về bởi cuộc gọi đầu tiên.
T.Tobler

Bạn nói đúng rằng thuật ngữ của tôi không chính xác, nhưng tôi nghĩ rằng sự phù hợp của một thứ gì đó chỉ là tài sản chỉ đọc chứ không phải là một phương pháp phụ thuộc vào việc thiếu tác dụng phụ, thay vì thuần túy.
supercat

2

Bạn có thể có thể giải quyết vấn đề bằng cách tạo một thuộc tính mới:

public new int Bar 
{            
    get { return 0; }
    set {}        
}

int IBase.Bar { 
  get { return Bar; }
}

4
Nhưng bạn chỉ có thể làm điều này khi thực hiện một giao diện, không phải khi kế thừa từ một lớp cơ sở. Bạn phải chọn một tên khác.
Svick

Tôi giả sử lớp ví dụ triển khai IBase (một cái gì đó giống như giao diện công cộng IBase {int Bar {get;}}), nếu không, trong lớp triển khai, int IBase.Bar {...} không được phép. Sau đó, bạn có thể chỉ cần tạo một thanh thuộc tính thông thường với cả get và set, sự kiện thuộc tính Bar từ giao diện không yêu cầu một bộ.
Ibrahim ben Salah

1

Tôi có thể hiểu tất cả các điểm của bạn, nhưng hiệu quả, các thuộc tính tự động của C # 3.0 trở nên vô dụng trong trường hợp đó.

Bạn không thể làm bất cứ điều gì như thế:

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get;
        private set;
    }
}

IMO, C # không nên hạn chế các kịch bản như vậy. Đó là trách nhiệm của nhà phát triển để sử dụng nó cho phù hợp.


Tôi không hiểu quan điểm của bạn. Bạn có đồng ý rằng C # nên cho phép thêm một setter, hoặc bạn với hầu hết những người khác đã trả lời câu hỏi này?
ripper234

2
Như tôi đã nói, IMO, C # nên cho phép thêm một setter. Đó là trách nhiệm của nhà phát triển để sử dụng nó cho phù hợp.
Thomas Danecker

1

Vấn đề là vì lý do nào Microsoft quyết định rằng cần có ba loại thuộc tính riêng biệt: chỉ đọc, chỉ viết và đọc, chỉ một trong số đó có thể tồn tại với một chữ ký nhất định trong ngữ cảnh cụ thể; các thuộc tính chỉ có thể bị ghi đè bởi các thuộc tính được khai báo giống hệt nhau. Để làm những gì bạn muốn, cần phải tạo hai thuộc tính có cùng tên và chữ ký - một trong số đó là chỉ đọc và một trong số đó là đọc-ghi.

Cá nhân, tôi muốn rằng toàn bộ khái niệm "thuộc tính" có thể được bãi bỏ, ngoại trừ cú pháp thuộc tính-ish có thể được sử dụng làm đường cú pháp để gọi các phương thức "get" và "set". Điều này không chỉ tạo điều kiện cho tùy chọn 'thêm bộ' mà còn cho phép 'nhận' trả về một loại khác từ 'bộ'. Mặc dù khả năng như vậy sẽ không được sử dụng thường xuyên, đôi khi có thể hữu ích khi phương thức 'get' trả về một đối tượng trình bao trong khi 'tập hợp' có thể chấp nhận cả trình bao bọc hoặc dữ liệu thực tế.


0

Đây là một cách giải quyết để đạt được điều này bằng Reflection:

var UpdatedGiftItem = // object value to update;

foreach (var proInfo in UpdatedGiftItem.GetType().GetProperties())
{
    var updatedValue = proInfo.GetValue(UpdatedGiftItem, null);
    var targetpropInfo = this.GiftItem.GetType().GetProperty(proInfo.Name);
    targetpropInfo.SetValue(this.GiftItem, updatedValue,null);
}

Bằng cách này, chúng ta có thể đặt giá trị đối tượng trên một thuộc tính chỉ đọc. Có thể không hoạt động trong tất cả các kịch bản mặc dù!


1
Bạn có chắc chắn rằng bạn có thể gọi một setter thông qua sự phản chiếu không tồn tại (như xảy ra trong các thuộc tính chỉ được mô tả)?
HOẶC Mapper

0

Bạn nên thay đổi tiêu đề câu hỏi của mình thành chi tiết rằng câu hỏi của bạn chỉ liên quan đến việc ghi đè một thuộc tính trừu tượng hoặc câu hỏi của bạn liên quan đến việc ghi đè lên một thuộc tính chỉ dành cho một lớp.


Nếu trước đây (ghi đè một tài sản trừu tượng)

Mã đó là vô dụng. Một lớp cơ sở một mình không nên nói với bạn rằng bạn buộc phải ghi đè một thuộc tính Chỉ nhận (Có lẽ là Giao diện). Một lớp cơ sở cung cấp chức năng chung có thể yêu cầu đầu vào cụ thể từ một lớp thực hiện. Do đó, chức năng phổ biến có thể thực hiện các cuộc gọi đến các thuộc tính hoặc phương thức trừu tượng. Trong trường hợp cụ thể, các phương thức chức năng phổ biến sẽ yêu cầu bạn ghi đè một phương thức trừu tượng, chẳng hạn như:

public int GetBar(){}

Nhưng nếu bạn không kiểm soát được điều đó và chức năng của lớp cơ sở đọc từ tài sản công cộng của chính nó (kỳ lạ), thì chỉ cần làm điều này:

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    private int _bar;
    public override int Bar
    {
        get { return _bar; }
    }
    public void SetBar(int value)
    {
        _bar = value;
    }
}

Tôi muốn chỉ ra nhận xét (kỳ lạ): Tôi muốn nói rằng một cách thực hành tốt nhất là cho một lớp không sử dụng các thuộc tính công khai của riêng nó, mà sử dụng các trường riêng / được bảo vệ khi chúng tồn tại. Vì vậy, đây là một mô hình tốt hơn:

public abstract class BaseClass {
    protected int _bar;
    public int Bar { get { return _bar; } }
    protected void DoBaseStuff()
    {
        SetBar();
        //Do something with _bar;
    }
    protected abstract void SetBar();
}

public class ConcreteClass : BaseClass {
    protected override void SetBar() { _bar = 5; }
}

Nếu sau này (ghi đè tài sản chỉ nhận của một lớp)

Mỗi thuộc tính không trừu tượng có một setter. Nếu không, nó vô dụng và bạn không nên quan tâm đến việc sử dụng nó. Microsoft không phải cho phép bạn làm những gì bạn muốn. Lý do là: setter tồn tại ở dạng này hay dạng khác, và bạn có thể thực hiện những gì bạn muốn Veerryy một cách dễ dàng.

Lớp cơ sở, hoặc bất kỳ lớp nào mà bạn có thể đọc một thuộc tính {get;}, có MỘT SỐ loại setter được hiển thị cho thuộc tính đó. Siêu dữ liệu sẽ trông như thế này:

public abstract class BaseClass
{
    public int Bar { get; }
}

Nhưng việc thực hiện sẽ có hai đầu của phổ phức tạp:

Tổ hợp nhỏ nhất:

public abstract class BaseClass
{
    private int _bar;
    public int Bar { 
        get{
            return _bar;
        }}
    public void SetBar(int value) { _bar = value; }
}

Phức tạp nhất:

public abstract class BaseClass
{
    private int _foo;
    private int _baz;
    private int _wtf;
    private int _kthx;
    private int _lawl;

    public int Bar
    {
        get { return _foo * _baz + _kthx; }
    }
    public bool TryDoSomethingBaz(MyEnum whatever, int input)
    {
        switch (whatever)
        {
            case MyEnum.lol:
                _baz = _lawl + input;
                return true;
            case MyEnum.wtf:
                _baz = _wtf * input;
                break;
        }
        return false;
    }
    public void TryBlowThingsUp(DateTime when)
    {
        //Some Crazy Madeup Code
        _kthx = DaysSinceEaster(when);
    }
    public int DaysSinceEaster(DateTime when)
    {
        return 2; //<-- calculations
    }
}
public enum MyEnum
{
    lol,
    wtf,
}

Quan điểm của tôi, dù bằng cách nào, bạn có bộ cài đặt tiếp xúc. Trong trường hợp của bạn, bạn có thể muốn ghi đèint Bar vì bạn không muốn lớp cơ sở xử lý nó, không có quyền truy cập để xem xét cách xử lý của nó hoặc được giao nhiệm vụ thực hiện một số mã thực sự nhanh chóng chống lại ý chí của bạn .

Trong cả Latter và Cựu (Kết luận)

Câu chuyện dài: Microsoft không cần thiết phải thay đổi bất cứ điều gì. Bạn có thể chọn cách lớp triển khai của bạn được thiết lập và, tìm kiếm hàm tạo, sử dụng tất cả hoặc không có lớp cơ sở nào.


0

Giải pháp cho chỉ một tập hợp con nhỏ của các trường hợp sử dụng, tuy nhiên: trong setter "chỉ đọc" C # 6.0 được tự động thêm vào cho các thuộc tính chỉ dành cho getter bị ghi đè.

public abstract class BaseClass
{
    public abstract int Bar { get; }
}

public class ConcreteClass : BaseClass
{
    public override int Bar { get; }

    public ConcreteClass(int bar)
    {
        Bar = bar;
    }
}

-1

Bởi vì điều đó sẽ phá vỡ khái niệm đóng gói và ẩn giấu thực hiện. Hãy xem xét trường hợp khi bạn tạo một lớp, gửi nó và sau đó người tiêu dùng của lớp của bạn tự tạo cho mình một tài sản mà ban đầu bạn chỉ cung cấp một getter. Nó có hiệu quả sẽ phá vỡ mọi bất biến của lớp mà bạn có thể phụ thuộc vào việc thực hiện.


1
-1: Có một setter không tự động cho phép một hậu duệ phá vỡ mọi bất biến. Setter vẫn chỉ có thể thay đổi những thứ mà con cháu đã thay đổi.
Roman Starkov

@RomanStarkov Điều đó đúng. Nhưng câu hỏi của bài này là; Tại sao không thể thêm một setter vào một bản tóm tắt mà hợp đồng mà bạn không làm. Nhận xét của bạn sẽ thích hợp trong câu hỏi ngược lại; Tại sao Microsoft cho phép bạn tạo một thuộc tính ReadOnly một cách trừu tượng nếu con cháu có toàn quyền kiểm soát các trường riêng của nó?
Suamere 17/03/2017

+1: Câu hỏi này là về lý do tại sao Microsoft không cho phép bạn phá vỡ hợp đồng. Mặc dù lý do cho các downvote của bạn là về lý do tại sao Microsoft cho phép hợp đồng chỉ bao gồm các thuộc tính chỉ đọc. Vì vậy, câu trả lời này là chính xác trong bối cảnh.
Suamere 17/03/2017

@Suamere hợp đồng không nói rằng tài sản không thể thay đổi. Tất cả những gì nó nói là có tồn tại một tài sản có thể được đọc. Thêm một setter sẽ không phá vỡ hợp đồng này. Bạn có thể hiểu đó là một ý định tạo ra một tài sản chỉ đọc, nhưng bạn không thể đảm bảo trong C # 6 rằng không có cách nào để thay đổi nó, do đó hợp đồng mà bạn nghĩ là không thực sự ở đó. Hãy suy nghĩ về một giao diện: nó có thể cung cấp một hợp đồng trên một tài sản có thể (không chỉ nhận!). Giao diện này có thể được thực hiện bởi một thuộc tính get + set tốt.
Roman Starkov 17/03/2017

Bạn đang tiếp cận điều này từ quan điểm của tác giả cụ thể. Anh ta có thể thay đổi nó khi anh ta hài lòng. Tôi đang tiếp cận nó từ quan điểm của tác giả lớp cơ sở -> Tiếp cận là người tiêu dùng kết quả cuối cùng của lớp thực hiện. Tác giả của lớp cơ sở không muốn bất kỳ người tiêu dùng nào của các lớp cụ thể trong tương lai có thể thay đổi thuộc tính đó mà không cần tác giả của lớp cụ thể đưa ra một phương thức đặc biệt để xử lý từng trường hợp cụ thể. Bởi vì trong lớp cơ sở, tài sản đó được coi là bất biến, và điều đó được truyền đạt bằng cách chỉ phơi bày nó như vậy.
Suamere

-1

Điều này không phải là không thể. Bạn chỉ cần sử dụng từ khóa "mới" trong tài sản của mình. Ví dụ,

namespace {
    public class Base {
        private int _baseProperty = 0;

        public virtual int BaseProperty {
            get {
                return _baseProperty;
            }
        }

    }

    public class Test : Base {
        private int _testBaseProperty = 5;

        public new int BaseProperty {
            get {
                return _testBaseProperty;
            }
            set {
                _testBaseProperty = value;
            }
        }
    }
}

Có vẻ như cách tiếp cận này thỏa mãn cả hai mặt của cuộc thảo luận này. Việc sử dụng "mới" phá vỡ hợp đồng giữa việc thực hiện lớp cơ sở và việc thực hiện lớp con. Điều này là cần thiết khi một Class có thể có nhiều hợp đồng (thông qua giao diện hoặc lớp cơ sở).

Hi vọng điêu nay co ich


29
Câu trả lời này là sai vì thuộc tính được đánh dấu newkhông còn ghi đè lên cơ sở ảo. Đặc biệt, nếu thuộc tính cơ sở là trừu tượng , như trong bài viết gốc, không thể sử dụng newtừ khóa trong một lớp con không trừu tượng vì bạn phải ghi đè tất cả các thành viên trừu tượng.
Timwi

Điều này cực kỳ nguy hiểm bởi vì các kết quả sau đây trả về các kết quả khác nhau mặc dù là cùng một đối tượng: Test t = new Test(); Base b = t; Console.WriteLine(t.BaseProperty)cho bạn 5, Console.WriteLine(b.BaseProperty)cho bạn 0 và khi người kế nhiệm của bạn phải gỡ lỗi, điều này tốt hơn là bạn nên di chuyển rất xa.
Mark Sowul

Tôi đồng ý với Mark, điều này cực kỳ khó chịu. Nếu cả hai triển khai BaseProperty đều được hỗ trợ bởi cùng một biến thì sẽ ổn dù IMHO. IE: Làm cho _baseProperty được bảo vệ và xóa bỏ _testBaseProperty. Ngoài ra, AFAIK việc triển khai trong Base không cần phải ảo vì bạn không thực sự ghi đè lên nó.
Steazy

Hầu hết các câu trả lời cho câu hỏi có thể có ý kiến ​​và đầu vào hợp lệ. Nhưng ngoài những bình luận trước đây, lời chỉ trích chính của tôi về câu trả lời này là tôi tin rằng câu hỏi thực tế có thể đúng theo nghĩa đen về các thuộc tính trừu tượng trong một cơ sở mã mà người tiêu dùng không kiểm soát được. Vì vậy, lời chỉ trích của tôi là câu trả lời này có thể không phù hợp với câu hỏi.
Suamere

-1

Một thuộc tính chỉ đọc trong lớp cơ sở chỉ ra rằng thuộc tính này đại diện cho một giá trị luôn có thể được xác định từ bên trong lớp (ví dụ: giá trị enum khớp với bối cảnh (db-) của một đối tượng). Vì vậy, khả năng đáp ứng của việc xác định giá trị vẫn nằm trong lớp.

Thêm một setter sẽ gây ra một vấn đề khó xử ở đây: Sẽ xảy ra lỗi xác thực nếu bạn đặt giá trị thành bất kỳ giá trị nào khác ngoài giá trị có thể có mà nó đã có.

Các quy tắc thường có ngoại lệ, mặc dù. Rất có thể là ví dụ trong một lớp dẫn xuất, bối cảnh thu hẹp các giá trị enum có thể xuống còn 3 trên 10, tuy nhiên người dùng của đối tượng này vẫn cần phải quyết định cái nào là đúng. Lớp dẫn xuất cần ủy thác khả năng đáp ứng xác định giá trị cho người dùng của đối tượng này. Điều quan trọng cần nhận ra là người dùng của đối tượng này cần nhận thức rõ về ngoại lệ này và giả sử khả năng đáp ứng để đặt giá trị chính xác.

Giải pháp của tôi trong các tình huống này là để lại thuộc tính chỉ đọc và thêm thuộc tính đọc-ghi mới vào lớp dẫn xuất để hỗ trợ ngoại lệ. Việc ghi đè tài sản ban đầu sẽ chỉ trả về giá trị của tài sản mới. Thuộc tính mới có thể có một tên thích hợp chỉ ra bối cảnh của ngoại lệ này một cách chính xác.

Điều này cũng hỗ trợ cho nhận xét hợp lệ: "làm cho khó hiểu nhất có thể để hiểu lầm" của Gishu.


1
Không có sự lúng túng sẽ được ngụ ý bởi một ReadableMatrixlớp (với một thuộc tính chỉ mục chỉ đọc hai đối số) có các kiểu con ImmutableMatrixMutableMatrix. Mã chỉ cần đọc một ma trận và không quan tâm nếu nó có thể thay đổi một thời gian trong tương lai, có thể chấp nhận một tham số loại ReadableMatrixvà hoàn toàn hài lòng với các kiểu con có thể thay đổi hoặc không thay đổi.
supercat

Một setter bổ sung có thể sửa đổi một biến riêng trong lớp. Thuộc tính được gọi là "chỉ đọc" từ lớp cơ sở sau đó vẫn sẽ xác định giá trị "từ bên trong lớp" (bằng cách đọc biến riêng tư mới). Tôi không thấy vấn đề ở đó. Ngoài ra, hãy nhìn vào, ví dụ, cái gì List<T>.Countkhông: Nó không thể được gán cho, nhưng điều đó không có nghĩa là nó "chỉ đọc" ở chỗ nó không thể được sửa đổi bởi người dùng của lớp. Nó rất có thể được sửa đổi, mặc dù gián tiếp, bằng cách thêm và xóa danh sách các mục.
HOẶC Mapper

-2

Bởi vì ở cấp độ IL, thuộc tính đọc / ghi chuyển thành hai phương thức (getter và setter).

Khi ghi đè, bạn phải tiếp tục hỗ trợ giao diện bên dưới. Nếu bạn có thể thêm một trình thiết lập, bạn thực sự sẽ thêm một phương thức mới, nó sẽ vô hình với thế giới bên ngoài, theo như giao diện của các lớp học của bạn có liên quan.

Đúng, thêm một phương thức mới sẽ không phá vỡ tính tương thích mỗi se, nhưng vì nó vẫn bị ẩn, quyết định không cho phép điều này có ý nghĩa hoàn hảo.


2
Tôi không quan tâm nhiều đến "thế giới bên ngoài" ở đây. Tôi muốn thêm chức năng cho một lớp, chỉ luôn hiển thị với mã biết lớp cụ thể chứ không phải cơ sở.
ripper234

@ ripper234 Xem câu trả lời của Matt bolt: nếu bạn muốn làm cho thuộc tính mới chỉ hiển thị cho người dùng của lớp dẫn xuất mà bạn nên sử dụng newthay vì override.
Dan Berindei

1
Nó không bị ẩn. Bạn muốn nói rằng bạn không thể thêm một phương thức mới vào lớp trẻ vì phương thức mới sẽ "bị ẩn khỏi thế giới bên ngoài". Tất nhiên nó vẫn bị ẩn khỏi phối cảnh của lớp cơ sở, nhưng nó có thể truy cập được từ các máy khách của lớp con. (Tức là ảnh hưởng đến những gì người nhận nên trả lại)
Sheepy

Sheepy, đó không phải là những gì tôi đang nói. Ý tôi là, chính xác như bạn đã chỉ ra, nó sẽ vẫn bị ẩn "từ góc độ của lớp cơ sở". Đó là lý do tại sao tôi nối "như xa như các lớp học của bạn Giao diện được quan tâm". Nếu bạn đang phân lớp một lớp cơ sở, gần như chắc chắn rằng "thế giới bên ngoài" sẽ đề cập đến các thể hiện đối tượng của bạn với loại lớp cơ sở. Nếu họ trực tiếp sử dụng lớp cụ thể của bạn, bạn sẽ không cần khả năng tương thích với lớp cơ sở và, bằng mọi cách, bạn có thể sử dụng newvới các bổ sung của mình.
Ishmaeel

-2

Bởi vì một lớp có thuộc tính chỉ đọc (không có setter) có thể có lý do chính đáng cho nó. Có thể không có bất kỳ kho dữ liệu cơ bản, ví dụ. Cho phép bạn tạo một setter phá vỡ hợp đồng do lớp quy định. Nó chỉ là OOP xấu.


Bình chọn. Điều này rất ngắn gọn. Tại sao Microsoft nên cho phép người tiêu dùng của một lớp cơ sở phá vỡ hợp đồng? Tôi đồng ý với phiên bản rất ngắn này của câu trả lời quá dài và khó hiểu của tôi. Cười lớn. Nếu bạn là người tiêu dùng của một lớp cơ sở, bạn không muốn phá vỡ hợp đồng, nhưng bạn CÓ THỂ thực hiện nó theo một vài cách khác nhau.
Suamere 17/03/2017
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.