Có bất kỳ lý do để sử dụng tài sản riêng trong C #?


245

Tôi chỉ nhận ra rằng C # bất động sản xây dựng cũng có thể được sử dụng với một tin sửa đổi lần truy cập:

private string Password { get; set; }

Mặc dù điều này là thú vị về mặt kỹ thuật, tôi không thể tưởng tượng được khi nào tôi sẽ sử dụng nó vì một lĩnh vực tư nhân liên quan đến lễ ít hơn :

private string _password;

và tôi không thể tưởng tượng khi tôi sẽ bao giờ cần để có thể nội có được nhưng không thiết lập hoặc thiết lập nhưng không có được một lĩnh vực riêng:

private string Password { get; }

hoặc là

private string Password { set; }

nhưng có lẽ có một trường hợp sử dụng với các lớp lồng nhau / kế thừa hoặc có lẽ trong đó một get / set có thể chứa logic thay vì chỉ trả lại giá trị của thuộc tính, mặc dù tôi có xu hướng giữ các thuộc tính đơn giản và để các phương thức rõ ràng thực hiện bất kỳ logic nào, ví dụ GetEncodedPassword().

Có ai sử dụng các thuộc tính riêng tư trong C # vì bất kỳ lý do nào không hay đây chỉ là một trong những cấu trúc mã kỹ thuật có thể có nhưng hiếm khi được sử dụng trong thực tế?

Phụ lục

Những câu trả lời hay, đọc qua chúng tôi đã loại bỏ những cách sử dụng này cho các thuộc tính riêng tư:

  • khi các lĩnh vực tư nhân cần được tải một cách lười biếng
  • khi các trường riêng cần thêm logic hoặc được tính giá trị
  • vì các lĩnh vực riêng tư có thể khó gỡ lỗi
  • để "trình bày hợp đồng với chính mình"
  • để chuyển đổi nội bộ / đơn giản hóa một thuộc tính được phơi bày như là một phần của tuần tự hóa
  • gói các biến toàn cục được sử dụng trong lớp của bạn

Kỹ thuật được khuyến khích bởi các thuộc tính riêng là tự đóng gói - xem: sourcemaking.com/refactoring/elf-encapsulation-field
LBushkin

Câu trả lời:


212

Tôi sử dụng chúng nếu tôi cần lưu trữ một giá trị và muốn lười tải nó.

private string _password;
private string Password
{
    get
    {
        if (_password == null)
        {
            _password = CallExpensiveOperation();
        }

        return _password;
    }
}

42
một mô hình phổ biến tốt đẹp cho điều này làreturn _password ?? (_password = CallExpensiveOperation());
Marc

1
@EvAlex Lười <T> có thể thích hợp hơn nếu bạn có thể sử dụng nó. Nhưng nó chỉ có thể sử dụng công cụ tĩnh, vì vậy bạn không thể truy cập các phương thức (không tĩnh) hoặc các thành viên khác. Bằng cách này, tôi thích cú pháp dòng duy nhất return _password ?? (_password = CallExpensiveOperation());trong một thời gian. Hoặc thực sự Resharper thích nó :).
Bart

4
@Marc kể từ C # 6, bạn thậm chí không phải viết và nhận. private string Password => _password ?? (_password = CallExpensiveOperation());
Otto

1
Với C # 8, nó thậm chí còn ngắn hơn:private string Password => _password ??= CallExpensiveOperation();
Dave M

2
@Bas Đó không phải là tải lười biếng vì CallExpensiveOperation();được gọi trong quá trình xây dựng / khởi tạo đối tượng chứa và không phải khi tài sản được truy cập lần đầu tiên.
Stefan Podskubka

142

Việc sử dụng chính của mã này là khởi tạo lười biếng, như những người khác đã đề cập.

Một lý do khác cho các thuộc tính riêng tư trên các trường là các thuộc tính riêng tư dễ gỡ lỗi hơn nhiều so với các trường riêng. Tôi thường muốn biết những điều như "trường này đang được đặt bất ngờ; ai là người gọi đầu tiên đặt trường này?" và nó dễ dàng hơn nếu bạn có thể đặt một điểm dừng trên setter và nhấn go. Bạn có thể đặt đăng nhập vào đó. Bạn có thể đặt số liệu hiệu suất trong đó. Bạn có thể đặt các kiểm tra tính nhất quán chạy trong bản dựng gỡ lỗi.

Về cơ bản, nó đi xuống: mã mạnh hơn nhiều so với dữ liệu . Bất kỳ kỹ thuật nào cho phép tôi viết mã tôi cần là một kỹ năng tốt. Các trường không cho phép bạn viết mã trong đó, các thuộc tính làm.


5
Là "mã mạnh hơn nhiều so với dữ liệu" câu nói của bạn? Googling nó trả về các tài liệu tham khảo cho bạn. Chỉ muốn biết để tôi có thể trích dẫn chính xác khi tôi cần.
Joan Venge

24
@Joan: Tôi không biết. Hoặc là tôi đã làm cho nó lên, hoặc tôi nghe người khác nói điều đó và nghĩ "wow, tôi nên hoàn toàn ăn cắp điều đó, và sau đó quên tất cả về người mà tôi đã đánh cắp nó."
Eric Lippert

1
+ CodeLens cho bạn biết nơi tài sản được tham chiếu
UuDdLrLrSs

43

có lẽ có một trường hợp sử dụng với các lớp lồng nhau / được kế thừa hoặc có lẽ trong đó một get / set có thể chứa logic thay vì chỉ trả lại giá trị của thuộc tính

Cá nhân tôi sử dụng điều này ngay cả khi tôi không cần logic trên getter hoặc setter của một thuộc tính. Sử dụng một thuộc tính, thậm chí là một tài sản riêng, sẽ giúp chứng minh mã của bạn trong tương lai để bạn có thể thêm logic vào một getter sau, nếu cần.

Nếu tôi cảm thấy rằng một tài sản cuối cùng có thể yêu cầu thêm logic, đôi khi tôi sẽ bọc nó thành một tài sản riêng thay vì sử dụng một trường, vì vậy tôi không phải thay đổi mã của mình sau này.


Trong trường hợp bán liên quan (mặc dù khác với câu hỏi của bạn), tôi rất thường xuyên sử dụng các setters riêng trên các thuộc tính công cộng:

public string Password 
{
    get; 
    private set;
}

Điều này cung cấp cho bạn một getter công khai, nhưng giữ cho setter riêng tư.


+1 có ý nghĩa: "Nếu tôi cảm thấy rằng một tài sản cuối cùng có thể yêu cầu thêm logic, đôi khi tôi sẽ bọc nó thành một tài sản riêng thay vì sử dụng một trường, vì vậy tôi không phải thay đổi mã của mình sau này."
Edward Tanguay

7
setters riêng <3
Earlz

20

Một cách sử dụng tốt cho các thuộc tính chỉ nhận riêng là các giá trị được tính toán. Một vài lần tôi đã có các thuộc tính chỉ riêng tư và chỉ thực hiện một phép tính trên các trường khác trong loại của tôi. Nó không xứng đáng với một phương thức và không thú vị đối với các lớp khác vì vậy nó là tài sản riêng.


20

Khởi tạo lười biếng là một nơi mà chúng có thể gọn gàng, vd

private Lazy<MyType> mytype = new Lazy<MyType>(/* expensive factory function */);

private MyType MyType { get { return this.mytype.Value; } }

// In C#6, you replace the last line with: private MyType MyType => myType.Value;

Sau đó, bạn có thể viết: this.MyTypeở khắp mọi nơi chứ không phải this.mytype.Valuevà gói gọn thực tế rằng nó được khởi tạo một cách lười biếng ở một nơi duy nhất.

Một điều đáng xấu hổ là C # không hỗ trợ phân chia trường sao lưu cho thuộc tính (nghĩa là khai báo bên trong định nghĩa thuộc tính) để ẩn hoàn toàn và đảm bảo rằng nó chỉ có thể được truy cập thông qua thuộc tính.


2
Đồng ý rằng nó sẽ có khía cạnh phạm vi ở đó.
Chris Marisic

5
Tôi sử dụng kỹ thuật tương tự này thường xuyên và tôi cũng ước rằng một trường có thể nằm trong phạm vi cơ thể mã. Đó là một tính năng tốt nhưng ưu tiên thấp.
Eric Lippert

5
@Eric Lippert - field-declarations phạm vi trong phạm vi accessor-declarationsđã xếp thứ 1 trong danh sách mong muốn C # của tôi trong một thời gian dài. Nếu bạn có thể có được thiết kế và thực hiện trong một số phiên bản (thực tế) trong tương lai, thì tôi sẽ nướng cho bạn một chiếc bánh.
Jeffrey L Whitledge

13

Cách sử dụng duy nhất mà tôi có thể nghĩ đến

private bool IsPasswordSet 
{ 
     get
     {
       return !String.IsNullOrEmpty(_password);
     }
}

+1 cho lớp thuộc tính hữu ích được tính từ các biến riêng tư khác
Daren Thomas

2
Tại sao không sử dụng phương pháp riêng tưprivate bool IsPasswordSet() { return !String.IsNullOrEmpty(_password); }
Roman

10

Thuộc tính và trường không phải là một. Một thuộc tính là về giao diện của một lớp (cho dù nói về giao diện chung hay giao diện bên trong của nó), trong khi một trường là về việc triển khai của lớp. Các thuộc tính không nên được xem như là một cách để chỉ lộ ra các trường, chúng nên được xem như là một cách để phơi bày ý định và mục đích của lớp.

Giống như bạn sử dụng tài sản để trình bày hợp đồng cho người tiêu dùng về những gì tạo nên lớp của bạn, bạn cũng có thể trình bày hợp đồng cho chính mình vì những lý do rất giống nhau. Vì vậy, có, tôi sử dụng tài sản riêng khi nó có ý nghĩa. Đôi khi, một tài sản riêng có thể che giấu các chi tiết triển khai như lười tải, thực tế là một tài sản thực sự là một tập hợp của một số lĩnh vực và các khía cạnh, hoặc một tài sản cần phải được thực hiện ngay lập tức với mỗi cuộc gọi (nghĩ DateTime.Now). Chắc chắn có những lúc thật hợp lý khi thực thi điều này ngay cả với chính bạn trong phần phụ trợ của lớp.


+1: "bạn cũng có thể trình bày hợp đồng cho chính mình vì những lý do rất giống nhau" có ý nghĩa
Edward Tanguay

8

Tôi sử dụng chúng trong tuần tự hóa, với những thứ như DataContractSerializerhoặc protobuf-net hỗ trợ việc sử dụng này ( XmlSerializerkhông). Nó rất hữu ích nếu bạn cần đơn giản hóa một đối tượng như là một phần của tuần tự hóa:

public SomeComplexType SomeProp { get;set;}
[DataMember(Order=1)]
private int SomePropProxy {
    get { return SomeProp.ToInt32(); }
    set { SomeProp = SomeComplexType.FromInt32(value); }
}

6

Một điều tôi làm tất cả thời gian là lưu trữ các biến / bộ đệm "toàn cầu" vào HttpContext.Current

private static string SomeValue{
  get{
    if(HttpContext.Current.Items["MyClass:SomeValue"]==null){
      HttpContext.Current.Items["MyClass:SomeValue"]="";
    }
    return HttpContext.Current.Items["MyClass:SomeValue"];
  }
  set{
    HttpContext.Current.Items["MyClass:SomeValue"]=value;
  }
}

5

Tôi sử dụng chúng mọi lúc mọi nơi. Chúng có thể giúp gỡ lỗi mọi thứ dễ dàng hơn khi bạn có thể dễ dàng đặt điểm dừng trong thuộc tính hoặc bạn có thể thêm một câu lệnh ghi nhật ký, v.v.

Cũng có thể hữu ích nếu sau này bạn cần thay đổi loại dữ liệu của mình theo một cách nào đó hoặc nếu bạn cần sử dụng sự phản chiếu.


Như trên; nếu có logic liên quan đến get / set, đôi khi tôi có thể sử dụng thuộc tính riêng tư hoặc được bảo vệ. Nó thường phụ thuộc vào mức độ logic: logic đơn giản tôi sẽ thực hiện trong thuộc tính, rất nhiều logic tôi thường sử dụng hàm phụ trợ. Bất cứ điều gì làm cho mã duy trì nhất.
TechNeilogy

5

Tôi sử dụng các thuộc tính riêng tư để giảm mã để truy cập các thuộc tính phụ thường sử dụng.

    private double MonitorResolution
    {
        get { return this.Computer.Accesories.Monitor.Settings.Resolution; }
    }

Nó rất hữu ích nếu có nhiều thuộc tính phụ.


2

Đó là một thực tế phổ biến để chỉ sửa đổi các thành viên với các phương thức get / set, thậm chí cả phương thức riêng tư. Bây giờ, logic đằng sau điều này là để bạn biết get / set của bạn luôn hoạt động theo một cách cụ thể (ví dụ: bắn ra các sự kiện) dường như không có ý nghĩa vì những thứ đó sẽ không được đưa vào sơ đồ thuộc tính ... nhưng thói quen cũ chết cứng.


2

Nó có ý nghĩa hoàn hảo khi có logic liên quan đến tập thuộc tính hoặc get (nghĩ khởi tạo lười biếng) và thuộc tính được sử dụng ở một vài nơi trong lớp.

Nếu nó chỉ là một lĩnh vực sao lưu thẳng? Không có gì đến với tâm trí là một lý do tốt.


2

Tôi biết câu hỏi này rất cũ nhưng thông tin dưới đây không có trong bất kỳ câu trả lời hiện tại nào.

Tôi không thể tưởng tượng khi nào tôi cần có thể có được nội bộ nhưng không được thiết lập

Nếu bạn đang tiêm các phụ thuộc của mình, bạn có thể muốn có Getter trên một Tài sản và không phải là một setter vì điều này sẽ biểu thị một Tài sản chỉ đọc. Nói cách khác, Thuộc tính chỉ có thể được đặt trong hàm tạo và không thể thay đổi bởi bất kỳ mã nào khác trong lớp.

Ngoài ra Visual Studio Professional sẽ cung cấp thông tin về Thuộc tính chứ không phải trường giúp dễ dàng xem trường của bạn đang được sử dụng.

Sản phẩm porpField


1

Vâng, như không ai đề cập, bạn có thể sử dụng nó để xác thực dữ liệu hoặc khóa các biến.

  • Thẩm định

    string _password;
    string Password
    {
        get { return _password; }
        set
        {
            // Validation logic.
            if (value.Length < 8)
            {
                throw new Exception("Password too short!");
            }
    
            _password = value;
        }
    }
  • Khóa

    object _lock = new object();
    object _lockedReference;
    object LockedReference
    { 
        get
        {
            lock (_lock)
            {
                return _lockedReference;
            }
        }
        set
        {
            lock (_lock)
            {
                _lockedReference = value;
            }
        }
    }

    Lưu ý: Khi khóa tham chiếu, bạn không khóa quyền truy cập vào các thành viên của đối tượng được tham chiếu.

Tham khảo lười biếng: Khi lười tải, cuối cùng bạn có thể cần phải thực hiện async mà ngày nay có AsyncLazy . Nếu bạn đang dùng các phiên bản cũ hơn so với Visual Studio SDK 2015 hoặc không sử dụng nó, bạn cũng có thể sử dụng AsyncLazy của AsyncEx .


0

Một số cách sử dụng kỳ lạ hơn của các trường rõ ràng bao gồm:

  • bạn cần sử dụng refhoặc outvới giá trị - có lẽ vì đó là bộ Interlockedđếm
  • nó được dự định để thể hiện bố cục cơ bản, ví dụ như trên một structbố cục rõ ràng (có lẽ để ánh xạ tới một bãi chứa C ++ hoặc unsafemã)
  • về mặt lịch sử, loại đã được sử dụng với BinaryFormatterxử lý trường tự động (thay đổi thành đạo cụ tự động thay đổi tên và do đó phá vỡ bộ nối tiếp)
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.