it-swarm-vi.tech

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

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

130
ripper234

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à tài sản 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 giả định 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. Vấn đề 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 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ó thuộc tính đọ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'.

1
Gishu

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 { ... } }

khá rõ ràng rằng cả getset đều 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; }
31
Roman Starkov

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 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.

19
JJJ

Có thể

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

  1. Tạo thuộc tính new có cả getset sử dụng cùng tên.

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

Điều này cho phép chúng tôi ghi đè các thuộc tính bằng getset ngay cả khi chúng thiếu một thuộc tính 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ở chỉ là get-, 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ở chỉ là set-, thì bạn có thể sử dụng loại trả về có nguồn gốc ít hơn.

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

    • bạn có thể sử dụng loại trả về có nguồn gốc nhiều hơn _/ifbạn chỉ tạo set-;

    • bạn chỉ có thể sử dụng loại trả về có nguồn gốc ít hơnifbạn chỉ tạo 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 một kiểu trả về cho đơn giản.

Tình huống: Thuộc tính get- tồn tại trước

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 phương thức set 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ể override the get- chỉ với getset

Bạn muốn override với thuộc tính get/set, 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 lớp trung gian abstract

Trong khi bạn không thể trực tiếp override với thuộc tính get/set, bạn _/can:

  1. Tạo một thuộc tính newget/set có cùng tên.

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

Vì vậy, trước tiên, bạn viết lớp trung gian abstract:

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 bởi vì bạn không thực sự override 'trong tài sản chỉ get-; thay vào đó, bạn đang thay thế nó bằng từ khóa new.

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 hoạt động 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 D là một trong các lớp cơ sở của nó, ví dụ: A hoặ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 lớp cơ sở của get cuối cùng vẫn bị ghi đè bởi định nghĩa của get của lớp dẫn xuất, 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 các phương thức set vào các thuộc tính get-. 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- hoặc get- và -set, bất kể đó là gì trong một lớp cơ sở.

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

Hạn chế chính là có nhiều mã hóa hơn để làm và thêm abstract class 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.

12
Nat

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 vì quan niệm sai lầm tương tự đã đượ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ờ là 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ị trong lần tiếp theo 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;
   }
8
T.Tobler

Vấn đề là vì bất kỳ lý do gì 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 dữ liệu thực hoặc trình bao bọc.

1
supercat

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.

1
Thomas Danecker

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; }
}
1
Hallgrim

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 trình thiết lập "chỉ đọc" C # 6.0 được tự động thêm vào cho các thuộc tính chỉ 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;
    }
}
0
lxa

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 thuộc tính 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 cái sau (ghi đè lên thuộc tính 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 có {get;}, cóMỘT SỐloại trình thiết lập đượ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:

Phức tạp ít 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ụ xử lý một số mã thực sự nhanh chóng chống lại bạn sẽ.

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
Suamere

Đâ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ù!

0
user2514880