Tại sao tôi nên tránh sử dụng Thuộc tính trong C #?


102

Trong cuốn sách xuất sắc của mình, CLR Via C #, Jeffrey Richter nói rằng ông không thích tài sản và khuyến cáo không nên sử dụng chúng. Anh ấy đưa ra một số lý do, nhưng tôi không thực sự hiểu. Bất cứ ai có thể giải thích cho tôi tại sao tôi nên hoặc không nên sử dụng thuộc tính? Trong C # 3.0, với thuộc tính tự động, điều này có thay đổi không?

Để tham khảo, tôi đã thêm ý kiến ​​của Jeffrey Richter:

• Một thuộc tính có thể ở chế độ chỉ đọc hoặc chỉ ghi; truy cập trường luôn có thể đọc và ghi được. Nếu bạn xác định một thuộc tính, tốt nhất là cung cấp cả hai phương thức truy cập get và set.

• Một phương thức thuộc tính có thể đưa ra một ngoại lệ; truy cập trường không bao giờ ném một ngoại lệ.

• Một thuộc tính không thể được truyền dưới dạng tham số out hoặc ref cho một phương thức; một lĩnh vực có thể. Ví dụ: mã sau sẽ không biên dịch:

using System;
public sealed class SomeType
{
   private static String Name 
   {
     get { return null; }
     set {}
   }
   static void MethodWithOutParam(out String n) { n = null; }
   public static void Main()
   {
      // For the line of code below, the C# compiler emits the following:
      // error CS0206: A property or indexer may not
      // be passed as an out or ref parameter
      MethodWithOutParam(out Name);
   }
}

• Một phương thức thuộc tính có thể mất nhiều thời gian để thực thi; truy cập trường luôn hoàn thành ngay lập tức. Một lý do phổ biến để sử dụng thuộc tính là thực hiện đồng bộ hóa luồng, điều này có thể dừng luồng mãi mãi và do đó, không nên sử dụng thuộc tính nếu đồng bộ hóa luồng được yêu cầu. Trong tình huống đó, một phương pháp được ưu tiên hơn. Ngoài ra, nếu lớp của bạn có thể được truy cập từ xa (ví dụ: lớp của bạn có nguồn gốc từ System.MashalByRefObject), việc gọi phương thức thuộc tính sẽ rất chậm và do đó, một phương thức được ưu tiên hơn một thuộc tính. Theo tôi, các lớp có nguồn gốc từ MarshalByRefObject không bao giờ được sử dụng thuộc tính.

• Nếu được gọi nhiều lần liên tiếp, một phương thức thuộc tính có thể trả về một giá trị khác nhau mỗi lần; một trường trả về cùng một giá trị mỗi lần. Lớp System.DateTime có thuộc tính readonly Now trả về ngày và giờ hiện tại. Mỗi lần bạn truy vấn thuộc tính này, nó sẽ trả về một giá trị khác. Đây là một sai lầm và Microsoft mong muốn rằng họ có thể sửa lớp này bằng cách biến Now thành một phương thức thay vì một thuộc tính.

• Một phương pháp đặc tính có thể gây ra các phản ứng phụ có thể quan sát được; truy cập thực địa không bao giờ làm. Nói cách khác, người dùng của một loại sẽ có thể đặt các thuộc tính khác nhau được xác định bởi một loại theo bất kỳ thứ tự nào mà họ chọn mà không nhận thấy bất kỳ hành vi khác nhau nào trong loại.

• Một phương thức thuộc tính có thể yêu cầu thêm bộ nhớ hoặc trả về một tham chiếu đến một cái gì đó không thực sự là một phần của trạng thái của đối tượng, vì vậy việc sửa đổi đối tượng được trả về không có ảnh hưởng gì đến đối tượng ban đầu; truy vấn một trường luôn trả về một tham chiếu đến một đối tượng được đảm bảo là một phần của trạng thái của đối tượng ban đầu. Làm việc với một thuộc tính trả về một bản sao có thể rất khó hiểu đối với các nhà phát triển và đặc điểm này thường không được ghi lại.


11
Tôi sở hữu ấn bản thứ 3 của 'CLR qua C #' và trên trang 242, ông Richter nói rằng cá nhân ông không thích tài sản, nhưng ông không bao giờ khuyến nghị không sử dụng chúng. Vui lòng trích dẫn phiên bản sách và số trang nơi bạn đọc.
kirk.burleson

Câu trả lời:


173

Lý do không thích thuộc tính của Jeff là vì chúng trông giống như các trường - vì vậy các nhà phát triển không hiểu sự khác biệt sẽ coi chúng như thể chúng là các trường, cho rằng chúng sẽ rẻ để thực thi, v.v.

Cá nhân tôi không đồng ý với anh ấy về điểm cụ thể này - tôi thấy các thuộc tính làm cho mã máy khách dễ đọc hơn nhiều so với các lệnh gọi phương thức tương đương. Tôi đồng ý rằng các nhà phát triển cần biết rằng các thuộc tính về cơ bản là các phương thức được ngụy trang - nhưng tôi nghĩ rằng việc giáo dục các nhà phát triển về điều đó tốt hơn là làm cho mã khó đọc hơn bằng các phương thức. (Đặc biệt, khi thấy mã Java với một số bộ định tuyến và bộ định tuyến được gọi trong cùng một câu lệnh, tôi biết rằng mã C # tương đương sẽ dễ đọc hơn rất nhiều. Định luật Demeter về mặt lý thuyết là rất tốt, nhưng đôi khi foo.Name.Lengththực sự là như vậy. điều đúng đắn để sử dụng ...)

(Và không, các thuộc tính được triển khai tự động không thực sự thay đổi bất kỳ điều này.)

Điều này hơi giống các lập luận chống lại việc sử dụng các phương pháp mở rộng - tôi có thể hiểu lý do, nhưng lợi ích thực tế (khi sử dụng ít) vượt trội hơn nhược điểm theo quan điểm của tôi.


Cảm ơn rất nhiều cho câu trả lời của bạn! Về các lập luận chống lại việc sử dụng các phương pháp mở rộng: bạn đang nói về chủ đề nào đó của Jeffrey Richter hay là tóm tắt?
abatishchev

@abatishchev: Đó chỉ là một điểm chung về các phương pháp mở rộng. Nó không liên quan trực tiếp đến tài sản.
Jon Skeet

Tôi muốn đi xa hơn. Ngoại trừ khía cạnh hiệu suất, tôi không hiểu tại sao một lập trình viên nên BIẾT nếu một cái gì đó là một trường hay một thuộc tính. Anh ta nên nghĩ về nó như là một thuộc tính của instance chỉ định STATE của đối tượng, và việc triển khai đối tượng nên xử lý tất cả các sửa đổi đối với trạng thái của nó (trong hợp đồng của lớp). Vì vậy, các thuộc tính cũng có thể là các trường trong một số trường hợp, hoặc trở thành các trường trong những trường hợp khác, nếu có thể việc thiết kế lại việc triển khai của lớp được thực hiện. Sau đó, quyết định giữa một lĩnh vực hay một tài sản là vấn đề về mức độ bảo vệ của nhà nước để duy trì sự nhất quán.
TheBlastOne

1
@PatrickFromberg: Rõ ràng là bạn đã bỏ lỡ một lượng lớn mã sử dụng các trường chỉ đọc. Không có gì để nói rằng các thuộc tính bao hàm khả năng thay đổi. Tôi thường có các trường chỉ đọc hỗ trợ các thuộc tính chỉ đọc - bạn có nghĩ đó là một điều tồi tệ không?
Jon Skeet

1
@Patrick: Không, nó mang lại lợi ích của việc tách API khỏi việc triển khai - sau này bạn có thể thay đổi cách tính thuộc tính khỏi trường (ví dụ: tính hai thuộc tính liên quan từ một trường).
Jon Skeet

34

Vâng, chúng ta hãy xem xét từng lý lẽ của anh ấy:

Thuộc tính có thể chỉ đọc hoặc chỉ ghi; truy cập trường luôn có thể đọc và ghi được.

Đây là một chiến thắng cho các thuộc tính, vì bạn có quyền kiểm soát chi tiết hơn đối với quyền truy cập.

Một phương thức thuộc tính có thể ném ra một ngoại lệ; truy cập trường không bao giờ ném một ngoại lệ.

Mặc dù điều này hầu hết đúng, bạn rất có thể gọi một phương thức trên trường đối tượng không được khởi tạo và có một ngoại lệ được ném ra.

• Một thuộc tính không thể được truyền dưới dạng tham số out hoặc ref cho một phương thức; một lĩnh vực có thể.

Hội chợ.

• Một phương thức thuộc tính có thể mất nhiều thời gian để thực thi; truy cập trường luôn hoàn thành ngay lập tức.

Nó cũng có thể mất rất ít thời gian.

• Nếu được gọi nhiều lần liên tiếp, một phương thức thuộc tính có thể trả về một giá trị khác nhau mỗi lần; một trường trả về cùng một giá trị mỗi lần.

Không đúng. Làm thế nào để bạn biết giá trị của trường không thay đổi (có thể bởi một luồng khác)?

Lớp System.DateTime có thuộc tính readonly Now trả về ngày và giờ hiện tại. Mỗi lần bạn truy vấn thuộc tính này, nó sẽ trả về một giá trị khác. Đây là một sai lầm và Microsoft mong muốn rằng họ có thể sửa lớp này bằng cách biến Now thành một phương thức thay vì một thuộc tính.

Nếu đó là một sai lầm, đó là một lỗi nhỏ.

• Một phương pháp đặc tính có thể gây ra các phản ứng phụ có thể quan sát được; truy cập thực địa không bao giờ làm. Nói cách khác, người dùng của một loại sẽ có thể đặt các thuộc tính khác nhau được xác định bởi một loại theo bất kỳ thứ tự nào mà họ chọn mà không nhận thấy bất kỳ hành vi khác nhau nào trong loại.

Hội chợ.

• Một phương thức thuộc tính có thể yêu cầu bộ nhớ bổ sung hoặc trả về một tham chiếu đến một cái gì đó không thực sự là một phần của trạng thái của đối tượng, vì vậy việc sửa đổi đối tượng được trả về không ảnh hưởng đến đối tượng ban đầu; truy vấn một trường luôn trả về một tham chiếu đến một đối tượng được đảm bảo là một phần của trạng thái của đối tượng ban đầu. Làm việc với một thuộc tính trả về một bản sao có thể rất khó hiểu đối với các nhà phát triển và đặc điểm này thường không được ghi lại.

Hầu hết các phản đối đều có thể nói là đối với bộ cài và bộ cài của Java - và chúng tôi đã có chúng trong một thời gian khá lâu mà không gặp vấn đề như vậy trong thực tế.

Tôi nghĩ rằng hầu hết các vấn đề có thể được giải quyết bằng cách tô sáng cú pháp tốt hơn (tức là phân biệt các thuộc tính với các trường) để lập trình viên biết những gì mong đợi.


3
"các vấn đề có thể được giải quyết bằng cách tô sáng cú pháp tốt hơn" : Bạn có thường xuyên sử dụng các trường công khai không? Các trường riêng thường có kiểu khác, ví dụ _field. Hoặc thậm chí chỉ là chữ thường field.
Steven Jeuris

18

Tôi chưa đọc cuốn sách, và bạn chưa trích dẫn phần nào mà bạn không hiểu, vì vậy tôi sẽ phải đoán.

Một số người không thích các thuộc tính vì chúng có thể khiến mã của bạn làm những điều đáng ngạc nhiên.

Nếu tôi nhập Foo.Bar, những người đọc nó thường sẽ nghĩ rằng đây chỉ là truy cập vào một trường thành viên của lớp Foo. Đó là một hoạt động rẻ, gần như miễn phí, và nó có tính xác định. Tôi có thể gọi đi gọi lại, và lần nào cũng có kết quả như nhau.

Thay vào đó, với các thuộc tính, nó thực sự có thể là một lời gọi hàm. Nó có thể là một vòng lặp vô hạn. Nó có thể mở một kết nối cơ sở dữ liệu. Nó có thể trả về các giá trị khác nhau mỗi khi tôi truy cập.

Đó là một lập luận tương tự cho lý do tại sao Linus ghét C ++. Mã của bạn có thể gây ngạc nhiên cho người đọc. Anh ấy ghét việc nạp chồng toán tử: a + bkhông nhất thiết có nghĩa là bổ sung đơn giản. Nó có thể có nghĩa là một số hoạt động cực kỳ phức tạp, giống như thuộc tính C #. Nó có thể có tác dụng phụ. Nó có thể làm bất cứ điều gì.

Thành thật mà nói, tôi nghĩ đây là một lập luận yếu. Cả hai ngôn ngữ đều có đầy đủ những thứ như thế này. (Chúng ta cũng nên tránh quá tải toán tử trong C #? Sau cùng, đối số tương tự có thể được sử dụng ở đó)

Thuộc tính cho phép trừu tượng hóa. Chúng ta có thể giả vờ rằng một cái gì đó là một trường thông thường, và sử dụng nó như thể nó là một, và không phải lo lắng về những gì diễn ra ở hậu trường.

Đó thường được coi là một điều tốt, nhưng rõ ràng là nó phụ thuộc vào việc lập trình viên viết ra những câu trừu tượng có ý nghĩa. Thuộc tính của bạn sẽ hoạt động giống như các trường. Chúng không nên có tác dụng phụ, không nên thực hiện các thao tác tốn kém hoặc không an toàn. Chúng tôi muốn có thể coi chúng như những cánh đồng.

Tuy nhiên, tôi có một lý do khác để thấy chúng kém hoàn hảo. Chúng không thể được chuyển bằng cách tham chiếu đến các hàm khác.

Các trường có thể được chuyển dưới dạng ref, cho phép một hàm được gọi truy cập trực tiếp vào nó. Các hàm có thể được thông qua dưới dạng đại biểu, cho phép một hàm được gọi truy cập trực tiếp vào nó.

Thuộc tính ... không thể.

Tệ thật.

Nhưng điều đó không có nghĩa là các thuộc tính là xấu hoặc không nên được sử dụng. Đối với nhiều mục đích, chúng tuyệt vời.


3
Lập luận của tôi là bạn không nên cho rằng đó là một trường để bắt đầu - bởi vì nếu bạn đang gọi nó từ bất kỳ lớp nào khác, dù sao thì bạn cũng không nên có quyền truy cập vào các trường không phải hằng số, vì chúng phải là riêng tư. (Ngoài ra còn có quy ước đặt tên để thông báo cho bạn.)
Jon Skeet

1
Vâng tôi đồng ý. Đối số dường như sôi sục thành "Tôi đã quen với việc cú pháp này chỉ được sử dụng cho các trường. Do đó, việc mở rộng nó để bao gồm các trường hợp khác là không tốt". Và câu trả lời hiển nhiên là "Chà, hãy làm quen với việc nó bao gồm các trường hợp khác, và nó sẽ không tệ đâu". ;)
jalf

Tôi ước các ngôn ngữ .net sẽ cung cấp một phương tiện tiêu chuẩn mà theo đó một lớp có thể hiển thị các thuộc tính dưới dạng tham số ref; một thành viên (ví dụ Foo) của một biểu mẫu void Foo<T>(ActionByRef<Point,T> proc, ref T param)có thuộc tính đặc biệt và có trình biên dịch chuyển thing.Foo.X+=someVar;thành Foo((ref Point it, ref int param)=>it.X += param, ref someVar);. Vì lambda là một đại biểu tĩnh nên không cần đóng và người dùng một đối tượng sẽ có thể sử dụng bất kỳ vị trí lưu trữ nào đã hỗ trợ thuộc tính dưới dạng reftham số chính hãng (và có thể chuyển nó cho các phương thức khác dưới dạng reftham số).
supercat

Viết lambda bằng tay sẽ tạo ra mã nguồn thực sự bắt mắt, nhưng đó là lý do tại sao việc để trình biên dịch thực hiện chuyển đổi sẽ rất hữu ích. Mã cho một lớp để hiển thị thuộc tính "callback-by-ref" sẽ khá hợp lý. Mã chỉ xấu (không có biến đổi) ở phía người gọi.
supercat

Hãy hiểu rằng một thuộc tính là một lệnh gọi hàm được ngụy trang dưới dạng một thành viên, vì vậy, nó không thể được chuyển bằng tham chiếu bất kỳ hơn nào bạn có thể chuyển các hàm công khai của một lớp. Bạn luôn có thể sử dụng thành viên công khai nếu bạn định làm theo cách đó. Thuộc tính là một thành viên thông minh, nó có thể ném một ngoại lệ cho dữ liệu không hợp lệ được chuyển vào thiết lập.
Diceyus

17

Quay trở lại năm 2009, lời khuyên này chỉ đơn thuần giống như sự xoa dịu về loại phô mai Who Moved My Cheese . Ngày nay, nó gần như lỗi thời một cách nực cười.

Một điểm rất quan trọng mà nhiều câu trả lời dường như nhón gót nhưng không giải quyết được hết là những "mối nguy hiểm" có chủ đích của thuộc tính này là một phần có chủ ý của thiết kế khung!

Có, thuộc tính có thể:

  • Chỉ định các công cụ sửa đổi quyền truy cập khác nhau cho getter và setter. Đây là một lợi thế so với các lĩnh vực. Một mô hình phổ biến là có một bộ thu công cộng và một bộ cài đặt được bảo vệ hoặc nội bộ , một kỹ thuật kế thừa rất hữu ích mà không phải trường nào cũng có thể đạt được.

  • Ném một ngoại lệ. Cho đến nay, đây vẫn là một trong những phương pháp xác thực hiệu quả nhất, đặc biệt khi làm việc với các khuôn khổ giao diện người dùng liên quan đến các khái niệm ràng buộc dữ liệu. Khó hơn nhiều để đảm bảo rằng một đối tượng vẫn ở trạng thái hợp lệ khi làm việc với các trường.

  • Mất nhiều thời gian để thực hiện. So sánh hợp lệ ở đây là với các phương thức , mất nhiều thời gian như nhau - không phải các trường . Không có cơ sở nào được đưa ra cho tuyên bố "một phương pháp được ưu tiên" ngoài sở thích cá nhân của một tác giả.

  • Trả lại các giá trị khác nhau từ getter của nó trong các lần thực thi tiếp theo. Điều này gần giống như một trò đùa khi gần đến điểm ca ngợi các ưu điểm của ref/ outtham số với các trường, mà giá trị của một trường sau lệnh gọi ref/ outđược đảm bảo là khác nhiều so với giá trị trước đó của nó và không thể đoán trước được.

    Nếu chúng ta đang nói về trường hợp cụ thể (và thực tế về mặt học thuật) của truy cập đơn luồng không có khớp nối hướng tâm, chúng ta khá hiểu rằng nó chỉ là thiết kế thuộc tính tồi để có các tác dụng phụ thay đổi trạng thái có thể nhìn thấy và có thể trí nhớ của tôi mờ dần, nhưng tôi dường như không thể nhớ lại bất kỳ ví dụ nào về những người đang sử dụng DateTime.Nowvà mong đợi cùng một giá trị xuất hiện mọi lúc. Ít nhất là không có bất kỳ trường hợp nào mà họ sẽ không làm nó tồi tệ như một giả thuyết DateTime.Now().

  • Gây ra các phản ứng phụ có thể quan sát được - đó tất nhiên chính xác là lý do tại sao các thuộc tính được phát minh như một đặc điểm ngôn ngữ ngay từ đầu. Hướng dẫn Thiết kế Thuộc tính của chính Microsoft chỉ ra rằng thứ tự setter không quan trọng, vì nếu làm theo cách khác sẽ ngụ ý khớp nối tạm thời . Chắc chắn, bạn không thể đạt được kết hợp tạm thời với các trường một mình, nhưng đó chỉ bởi vì bạn không thể gây ra bất kỳ hành vi có ý nghĩa nào xảy ra với các trường một mình, cho đến khi một số phương thức được thực thi.

    Các trình truy cập thuộc tính thực sự có thể giúp ngăn chặn một số loại khớp nối tạm thời bằng cách buộc đối tượng vào trạng thái hợp lệ trước khi thực hiện bất kỳ hành động nào - ví dụ: nếu một lớp có a StartDatevà an EndDate, thì việc thiết lập EndDatetrước đó StartDatecũng có thể buộc StartDatetrở lại. Điều này đúng ngay cả trong môi trường đa luồng hoặc không đồng bộ, bao gồm cả ví dụ rõ ràng về giao diện người dùng hướng sự kiện.

Những thứ khác mà thuộc tính có thể thực hiện mà các trường không thể bao gồm:

  • Tải chậm , một trong những cách hiệu quả nhất để ngăn lỗi thứ tự khởi tạo.
  • Thay đổi Thông báo , là toàn bộ cơ sở cho kiến trúc MVVM .
  • Kế thừa , ví dụ xác định một lớp trừu tượng Typehoặc Namelớp dẫn xuất có thể cung cấp siêu dữ liệu thú vị nhưng không đổi về chúng.
  • Đánh chặn , nhờ những điều trên.
  • Indexers , mà tất cả những ai đã từng phải làm việc với COM interop và không thể tránh khỏi các Item(i)cuộc gọi sẽ nhận ra là một điều tuyệt vời.
  • Làm việc với PropertyDescriptor , điều cần thiết để tạo các nhà thiết kế và cho các khuôn khổ XAML nói chung.

Richter rõ ràng là một tác giả giàu kinh nghiệm và biết rất nhiều về CLR và C #, nhưng tôi phải nói rằng, có vẻ như khi anh ấy viết lời khuyên này ban đầu (tôi không chắc liệu nó có trong các bản sửa đổi gần đây của anh ấy hay không - tôi thực sự hy vọng là không) rằng anh ấy không muốn từ bỏ những thói quen cũ và gặp khó khăn khi chấp nhận các quy ước của C # (ví dụ: so với C ++).

Ý tôi muốn nói ở đây là, lập luận "thuộc tính được coi là có hại" của anh ấy về cơ bản tóm gọn lại thành một tuyên bố duy nhất: Các thuộc tính trông giống như trường, nhưng chúng có thể không hoạt động như trường. Và vấn đề với tuyên bố là, nó không đúng , hoặc tốt nhất là nó rất dễ gây hiểu lầm. Các thuộc tính trông không giống trường - ít nhất, chúng không được cho là giống trường.

Có hai quy ước mã hóa rất mạnh trong C # với các quy ước tương tự được chia sẻ bởi các ngôn ngữ CLR khác và FXCop sẽ hét vào mặt bạn nếu bạn không tuân theo chúng:

  1. Các trường phải luôn ở chế độ riêng tư, không bao giờ công khai.
  2. Các trường phải được khai báo trong camelCase. Thuộc tính là PascalCase.

Do đó, không có sự mơ hồ về việc liệu Foo.Bar = 42là một trình truy cập thuộc tính hay một trình truy cập trường. Nó là một trình truy cập thuộc tính và nên được xử lý như bất kỳ phương thức nào khác - nó có thể chậm, nó có thể tạo ra một ngoại lệ, v.v. Đó là bản chất của Abstraction - nó hoàn toàn phụ thuộc vào quyết định của lớp khai báo cách phản ứng. Các nhà thiết kế lớp nên áp dụng nguyên tắc ít gây ngạc nhiên nhất nhưng người gọi không nên giả định bất cứ điều gì về một thuộc tính ngoại trừ việc nó thực hiện những gì nó nói trên hộp thiếc. Đó là mục đích.

Phương thức thay thế cho thuộc tính là phương thức getter / setter ở khắp mọi nơi. Đó là cách tiếp cận của Java, và nó đã gây tranh cãi ngay từ đầu . Sẽ ổn nếu đó là túi của bạn, nhưng nó không phải là cách chúng tôi triển khai trong trại .NET. Chúng tôi cố gắng, ít nhất là trong giới hạn của một hệ thống được định kiểu tĩnh, để tránh những gì Fowler gọi là Tiếng ồn cú pháp . Chúng tôi không muốn có thêm dấu ngoặc đơn, thêm get/ setmụn cóc hoặc ký hiệu phương pháp phụ - không nếu chúng tôi có thể tránh chúng mà không làm mất đi sự rõ ràng.

Nói bất cứ điều gì bạn thích, nhưng foo.Bar.Baz = quux.Answers[42]sẽ luôn dễ đọc hơn rất nhiều foo.getBar().setBaz(quux.getAnswers().getItem(42)). Và khi bạn đọc hàng nghìn dòng này mỗi ngày, điều đó sẽ tạo nên sự khác biệt.

(Và nếu phản ứng tự nhiên của bạn đối với đoạn văn trên là nói, "chắc chắn rằng nó khó đọc, nhưng sẽ dễ dàng hơn nếu bạn chia nó thành nhiều dòng", thì tôi xin lỗi khi nói rằng bạn đã hoàn toàn bỏ sót ý. .)


11

Tôi không thấy bất kỳ lý do nào khiến bạn không nên sử dụng Thuộc tính nói chung.

Thuộc tính tự động trong C # 3+ chỉ đơn giản hóa cú pháp một chút (một đường tổng hợp).


làm ơn, hãy đọc các trang 215-216 trong cuốn sách đó. Tôi chắc rằng bạn biết ai là Jeffrey Richter!
Quán Mai

Tôi đã đọc (CLR thông qua C #, 2nd Ed.), Không đồng ý với quan điểm của mình và không thấy lý do thực sự không sử dụng thuộc tính!
abatishchev

Đó là một cuốn sách hay, nhưng tôi không đồng ý với tác giả trong thời điểm này.
Konstantin Tarkus

Đó chính xác là nơi mà tác giả đề nghị không sử dụng Thuộc tính? Đã không tìm thấy bất cứ điều gì trên p.215-217
Konstantin Tarkus

Tôi đã thêm ý kiến ​​của Jeffrey trong câu hỏi của tôi :). Bạn có thể tìm thấy nó ở các trang 215-216 trong bản in :)
Quan Mai

9

Đó chỉ là ý kiến ​​của một người. Tôi đã đọc khá nhiều sách c # và tôi chưa thấy ai khác nói "không sử dụng thuộc tính".

Cá nhân tôi nghĩ rằng thuộc tính là một trong những điều tốt nhất về c #. Chúng cho phép bạn hiển thị trạng thái thông qua bất kỳ cơ chế nào bạn muốn. Bạn có thể lười biếng khởi tạo lần đầu tiên một thứ gì đó được sử dụng và bạn có thể thực hiện xác thực khi đặt giá trị, v.v. Khi sử dụng và viết chúng, tôi chỉ nghĩ về các thuộc tính là bộ định tuyến và bộ định vị có cú pháp đẹp hơn nhiều.

Còn những điều kỵ với tài sản thì có đôi. Một có lẽ là sử dụng sai các thuộc tính, khác có thể là thiếu tinh tế.

Thứ nhất, thuộc tính là các loại phương thức. Có thể ngạc nhiên nếu bạn đặt logic phức tạp vào một thuộc tính vì hầu hết người dùng của một lớp sẽ mong đợi thuộc tính này khá nhẹ.

Ví dụ

public class WorkerUsingMethod
{
   // Explicitly obvious that calculation is being done here
   public int CalculateResult()
   { 
      return ExpensiveLongRunningCalculation();
   }
}

public class WorkerUsingProperty
{
   // Not at all obvious.  Looks like it may just be returning a cached result.
   public int Result
   {
       get { return ExpensiveLongRunningCalculation(); }
   }
}

Tôi thấy rằng việc sử dụng các phương pháp cho những trường hợp này sẽ giúp phân biệt.

Thứ hai, và quan trọng hơn, các thuộc tính có thể có tác dụng phụ nếu bạn đánh giá chúng trong khi gỡ lỗi.

Giả sử bạn có một số tài sản như thế này:

public int Result 
{ 
   get 
   { 
       m_numberQueries++; 
       return m_result; 
   } 
}

Bây giờ, giả sử bạn có một ngoại lệ xảy ra khi có quá nhiều truy vấn được thực hiện. Đoán xem điều gì sẽ xảy ra khi bạn bắt đầu gỡ lỗi và cuộn qua thuộc tính trong trình gỡ lỗi? Những điều tồi tệ. Tránh làm điều này! Nhìn vào thuộc tính thay đổi trạng thái của chương trình.

Đây là những cảnh báo duy nhất tôi có. Tôi nghĩ rằng lợi ích của tài sản vượt xa các vấn đề.


6

Lý do đó phải được đưa ra trong một bối cảnh rất cụ thể. Thường thì ngược lại - bạn nên sử dụng các thuộc tính vì chúng cung cấp cho bạn mức độ trừu tượng cho phép bạn thay đổi hành vi của một lớp mà không ảnh hưởng đến các máy khách của nó ...


+1 để ẩn ý làm nổi bật tầm quan trọng của "hợp đồng" giữa máy khách và thuộc tính lớp.
TheBlastOne

6

Tôi không thể không tìm hiểu chi tiết về ý kiến ​​của Jeffrey Richter:

Thuộc tính có thể chỉ đọc hoặc chỉ ghi; truy cập trường luôn có thể đọc và ghi được.

Sai: Các trường có thể được đánh dấu là chỉ đọc nên chỉ phương thức khởi tạo của đối tượng mới có thể ghi vào chúng.

Một phương thức thuộc tính có thể ném ra một ngoại lệ; truy cập trường không bao giờ ném một ngoại lệ.

Sai: Việc triển khai một lớp có thể thay đổi công cụ sửa đổi truy cập của một trường từ công khai thành riêng tư. Nỗ lực đọc các trường riêng tư trong thời gian chạy sẽ luôn dẫn đến một ngoại lệ.


5

Tôi không đồng ý với Jeffrey Richter, nhưng tôi có thể đoán tại sao anh ấy không thích tài sản (tôi chưa đọc sách của anh ấy).

Mặc dù vậy, các thuộc tính cũng giống như các phương thức (thực thi), với tư cách là người dùng của một lớp, tôi mong đợi rằng các thuộc tính của nó hoạt động "ít nhiều" giống như một trường công khai, ví dụ:

  • không có thao tác tốn thời gian nào diễn ra bên trong property getter / setter
  • property getter không có tác dụng phụ (gọi nó nhiều lần, không thay đổi kết quả)

Thật không may, tôi đã thấy các thuộc tính không hoạt động theo cách đó. Nhưng vấn đề không phải là bản thân các tài sản, mà là những người thực hiện chúng. Vì vậy, nó chỉ cần một số giáo dục.


1
+1 vì nó nêu bật tầm quan trọng của một hợp đồng sạch. Có một thuộc tính dường như chỉ đọc thường xuyên trả về một giá trị khác nhau trên mỗi tham chiếu là một thuộc tính ghi đọc - nó chỉ không ghi trên giá trị của chính nó mà là các biến phiên bản khác (trực tiếp hoặc gián tiếp). Đó không phải là phong cách xấu, nhưng phải được lập thành văn bản (hoặc là một phần ngầm của hợp đồng).
TheBlastOne

4

Đã có lúc tôi xem xét việc không sử dụng thuộc tính và đó là cách viết mã .Net Compact Framework. Trình biên dịch CF JIT không thực hiện tối ưu hóa giống như trình biên dịch JIT trên máy tính để bàn và không tối ưu hóa các trình truy cập thuộc tính đơn giản, vì vậy trong trường hợp này, việc thêm một thuộc tính đơn giản sẽ gây ra một lượng nhỏ mã phình ra khi sử dụng trường công khai. Thông thường đây sẽ không phải là một vấn đề, nhưng hầu như luôn luôn trong thế giới Compact Framework, bạn phải chống lại những hạn chế về bộ nhớ chặt chẽ, vì vậy, ngay cả những khoản tiết kiệm nhỏ như thế này cũng được tính.


4

Bạn không nên tránh sử dụng chúng nhưng bạn nên sử dụng chúng với trình độ chuyên môn và sự cẩn thận, vì những lý do được đưa ra bởi những người đóng góp khác.

Tôi đã từng thấy một thuộc tính có tên giống như Khách hàng đã mở nội bộ một cuộc gọi ngoài quy trình đến cơ sở dữ liệu và đọc danh sách khách hàng. Mã khách hàng có 'for (int i to Customer.Count)', điều này gây ra một lệnh gọi riêng đến cơ sở dữ liệu trên mỗi lần lặp và cho quyền truy cập của Khách hàng đã chọn. Đó là một ví dụ nghiêm trọng chứng minh nguyên tắc giữ tài sản rất nhẹ - hiếm hơn là truy cập trường nội bộ.

Một đối số CHO việc sử dụng thuộc tính là chúng cho phép bạn xác thực giá trị đang được đặt. Một điều khác là giá trị của thuộc tính có thể là một giá trị bắt nguồn, không phải là một trường đơn lẻ, như TotalValue = amount * số lượng.


3

Cá nhân tôi chỉ sử dụng thuộc tính khi tạo các phương thức get / set đơn giản. Tôi đã đi lạc khỏi nó khi đến với cấu trúc dữ liệu phức tạp.


3

Đối số giả định rằng các thuộc tính là xấu vì chúng trông giống như các trường, nhưng có thể thực hiện các hành động đáng ngạc nhiên. Giả định này bị vô hiệu bởi các kỳ vọng của lập trình viên .NET:

Thuộc tính không giống như trường. Các trường giống như thuộc tính.

• Một phương thức thuộc tính có thể đưa ra một ngoại lệ; truy cập trường không bao giờ ném một ngoại lệ.

Vì vậy, một trường giống như một thuộc tính được đảm bảo không bao giờ có ngoại lệ.

• Một thuộc tính không thể được truyền dưới dạng tham số out hoặc ref cho một phương thức; một lĩnh vực có thể.

Vì vậy, một trường giống như một thuộc tính, nhưng nó có các khả năng bổ sung: truyền đến a ref/ outcác phương thức chấp nhận.

• Một phương thức thuộc tính có thể mất nhiều thời gian để thực thi; truy cập trường luôn hoàn thành ngay lập tức. [...]

Vì vậy, một lĩnh vực giống như một tài sản nhanh.

• Nếu được gọi nhiều lần liên tiếp, một phương thức thuộc tính có thể trả về một giá trị khác nhau mỗi lần; một trường trả về cùng một giá trị mỗi lần. Lớp System.DateTime có thuộc tính readonly Now trả về ngày và giờ hiện tại.

Vì vậy, một trường giống như một thuộc tính được đảm bảo trả về cùng một giá trị trừ khi trường được đặt thành một giá trị khác.

• Một phương pháp đặc tính có thể gây ra các phản ứng phụ có thể quan sát được; truy cập thực địa không bao giờ làm.

Một lần nữa, một trường là một thuộc tính được đảm bảo không làm điều đó.

• Một phương thức thuộc tính có thể yêu cầu bộ nhớ bổ sung hoặc trả về một tham chiếu đến một cái gì đó không thực sự là một phần của trạng thái của đối tượng, vì vậy việc sửa đổi đối tượng được trả về không ảnh hưởng đến đối tượng ban đầu; truy vấn một trường luôn trả về một tham chiếu đến một đối tượng được đảm bảo là một phần của trạng thái của đối tượng ban đầu. Làm việc với một thuộc tính trả về một bản sao có thể rất khó hiểu đối với các nhà phát triển và đặc điểm này thường không được ghi lại.

Điều này có thể đáng ngạc nhiên, nhưng không phải vì nó là một tài sản làm được điều này. Thay vào đó, hầu như không ai trả lại các bản sao có thể thay đổi trong các thuộc tính, vì vậy trường hợp 0,1% là đáng ngạc nhiên.


0

Các phương thức gọi thay vì thuộc tính làm giảm đáng kể khả năng đọc của mã gọi. Trong J #, ví dụ, sử dụng ADO.NET là một cơn ác mộng vì Java không hỗ trợ các thuộc tính và chỉ mục (về cơ bản là các thuộc tính có đối số). Mã kết quả cực kỳ xấu xí, với các lệnh gọi phương thức trong dấu ngoặc đơn trống ở khắp nơi.

Hỗ trợ các thuộc tính và chỉ mục là một trong những lợi thế cơ bản của C # so với Java.

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.