Nếu thuộc tính có tác dụng phụ


19

Các thuộc tính trong C # có nên có tác dụng phụ bên cạnh việc thông báo về sự thay đổi trạng thái của nó không?

Tôi đã thấy các thuộc tính được sử dụng theo nhiều cách khác nhau. Từ các thuộc tính sẽ tải giá trị lần đầu tiên chúng được truy cập vào các thuộc tính có tác dụng phụ lớn như gây ra chuyển hướng đến một trang khác.



1
Trong số những phát hiện của @ Job, tại sao tôi nên tránh sử dụng Thuộc tính trong C #? aka ý kiến ​​của Jeffrey Richter rất phù hợp với câu hỏi này.
rwong

@rwong Có liên quan có nhưng hoàn toàn không có ý nghĩa - dù sao cũng phải đọc (để nhận thức được các vấn đề anh ấy nhấn mạnh). Ít nhất về vấn đề này tôi đang ở trong trại Skeet.
Apoorv Khurasia

Mặc dù điều này liên quan đến các nhà xây dựng, nhưng điều này cũng có liên quan: lập trình
Jim G.

Câu trả lời:


40

Tôi giả sử bạn đang nói về chỉ đọc tài sản, hoặc tại khách sạn ít nhất thu khí , kể từ khi một tài sản setter là, trong hầu hết mọi trường hợp, sẽ có tác dụng phụ. Nếu không thì nó không hữu ích lắm.

Nói chung, một thiết kế tốt theo nguyên tắc ít bất ngờ nhất . Đừng làm những việc mà người gọi không mong đợi bạn làm, đặc biệt là nếu những điều đó có thể thay đổi kết quả trong tương lai .

Nói chung , điều đó có nghĩa là các thuộc tính getters không nên có tác dụng phụ.

Tuy nhiên , chúng ta hãy cẩn thận về ý nghĩa của "tác dụng phụ".

Một tác dụng phụ là, về mặt kỹ thuật, bất kỳ sửa đổi của nhà nước. Đó có thể là trạng thái có thể truy cập công khai hoặc ... nó có thể là trạng thái hoàn toàn riêng tư.

Bộ tải lười / trì hoãn là một ví dụ về trạng thái gần như chỉ riêng tư. Miễn là trách nhiệm của người gọi không phải giải phóng tài nguyên đó, thì bạn thực sự đang giảm bớt sự bất ngờ và sự phức tạp nói chung bằng cách sử dụng khởi tạo hoãn lại. Một người gọi thường không mong muốn phải báo hiệu rõ ràng việc khởi tạo cấu trúc bên trong . Vì vậy, lười khởi tạo không vi phạm nguyên tắc trên.

Một ví dụ khác là một tài sản đồng bộ. Để một phương thức được an toàn cho luồng, nó thường sẽ phải được bảo vệ bởi phần quan trọng hoặc mutex. Bước vào một phần quan trọng hoặc có được một mutex một tác dụng phụ; bạn đang sửa đổi nhà nước, thường là nhà nước toàn cầu . Tuy nhiên, tác dụng phụ này là cần thiết để ngăn chặn một loại bất ngờ tồi tệ hơn nhiều - sự bất ngờ của dữ liệu bị sửa đổi (hoặc tệ hơn, được sửa đổi một phần) bởi một luồng khác.

Vì vậy, tôi sẽ nới lỏng một chút hạn chế sau đây: Việc đọc thuộc tính không được có tác dụng phụ có thể nhìn thấy hoặc tác dụng phụ làm thay đổi ngữ nghĩa của chúng .

Nếu không có cách nào khả thi để người gọi bị ảnh hưởng, hoặc thậm chí nhận thức được tác dụng phụ, thì tác dụng phụ đó không gây hại gì. Nếu bạn không thể viết một bài kiểm tra để xác minh sự tồn tại của một hiệu ứng phụ cụ thể, thì nó đủ bản địa hóa để gắn nhãn là một chi tiết thực hiện riêng tư, và do đó không có mối quan tâm chính đáng nào với thế giới bên ngoài.

Nhưng hãy cẩn thận; như một quy tắc tự nhiên, bạn nên cố gắng tránh các tác dụng phụ, bởi vì thường thì những gì bạn có thể nghĩ là một chi tiết thực hiện riêng tư có thể bất ngờ bị rò rỉ và trở nên công khai.


2
+1 - một câu trả lời hoàn chỉnh thú vị bao gồm các trường hợp phức tạp hơn biến "sẽ" thành "nên ... ngoại trừ khi ...."
quick_now

Một ví dụ khác về tác dụng phụ có thể chấp nhận được trên getter: Hàm ghi nhật ký.
Loren Pechtel

5

Tôi sẽ trả lời câu hỏi của bạn bằng một câu hỏi: Điều gì xảy ra khi bạn sửa đổi thuộc tính Độ rộng của biểu mẫu?

Chỉ cần ra khỏi đỉnh đầu của tôi, điều này sẽ:

  • Thay đổi giá trị của trường sao lưu.
  • Cháy một sự kiện.
  • Thay đổi kích thước của biểu mẫu, báo cáo thay đổi cho trình quản lý cửa sổ và yêu cầu sơn lại.
  • Thông báo cho các điều khiển về hình thức thay đổi để bất kỳ ai trong số họ cần thay đổi kích thước hoặc vị trí khi biểu mẫu được thay đổi kích thước có thể làm như vậy.
  • Nguyên nhân tất cả các điều khiển đã thay đổi thành các sự kiện và báo cho người quản lý cửa sổ rằng chúng cần được vẽ lại.
  • Có lẽ những thứ khác là tốt.

(Tuyên bố miễn trừ trách nhiệm: Tôi là nhà phát triển Delphi, không phải nhà phát triển .NET. Điều này có thể không chính xác 100% cho C #, nhưng tôi cá là nó khá gần, đặc biệt là xem xét bao nhiêu .NET framework gốc dựa trên Delphi.)

Tất cả các "tác dụng phụ" này xảy ra khi bạn thay đổi thuộc tính Width trên một biểu mẫu. Có ai trong số họ có vẻ không phù hợp hoặc không chính xác bằng cách nào đó?


miễn là nó không đi vào một vòng lặp vô hạn gọi chính nó. Để tránh điều này, mỗi "thay đổi" sẽ được ánh xạ tới ít nhất ba sự kiện: một sự kiện được bắn trước khi thay đổi, một lần bị bắn trong khi thay đổi và một lần bị bắn sau khi kết thúc.
rwong

5

Hãy xem Quy tắc thiết kế của Microsoft , đặc biệt là một trong số đó:

CA1024: Sử dụng thuộc tính khi thích hợp

... Một phương pháp là một ứng cử viên tốt để trở thành một tài sản nếu có một trong những điều kiện sau:

  • Không có đối số và trả về thông tin trạng thái của một đối tượng.
  • Chấp nhận một đối số duy nhất để đặt một phần trạng thái của một đối tượng.

Các thuộc tính nên hành xử như thể chúng là các trường; nếu phương thức không thể, nó không nên được thay đổi thành thuộc tính. Các phương thức tốt hơn các thuộc tính trong các tình huống sau:

  • Phương pháp thực hiện một hoạt động tốn thời gian. Phương pháp chậm hơn đáng kể so với thời gian cần thiết để đặt hoặc nhận giá trị của một trường.
  • Phương thức thực hiện chuyển đổi. Truy cập vào một trường không trả về một phiên bản chuyển đổi của dữ liệu mà nó lưu trữ.
  • Phương thức Get có tác dụng phụ có thể quan sát được. Lấy ra giá trị của một trường không tạo ra bất kỳ tác dụng phụ nào.
  • Trình tự thực hiện là quan trọng. Đặt giá trị của một trường không phụ thuộc vào sự xuất hiện của các hoạt động khác.
  • Gọi phương thức hai lần liên tiếp sẽ tạo ra kết quả khác nhau.
  • Phương thức này là tĩnh nhưng trả về một đối tượng có thể được người gọi thay đổi. Lấy ra giá trị của một trường không cho phép người gọi thay đổi dữ liệu được lưu trữ bởi trường.
  • Phương thức trả về một mảng ...

2

Tôi tin rằng tài sản không nên có tác dụng phụ. Tuy nhiên, có một ngoại lệ cho quy tắc này: lười tải. Nếu bạn muốn lười tải một cái gì đó trong một tài sản, điều đó tốt, bởi vì khách hàng không thể nói rằng việc tải lười biếng đang diễn ra, và nói chung là không quan tâm.

EDIT : Ồ, một điều nữa - bắn một sự kiện có nội dung "PropertyXYZ đã thay đổi!" (ví dụ INotifyPropertyChanged) - là tốt trên setters.


2

Ngoài việc tải lười biếng đã đề cập trước đó, còn có trường hợp chức năng ghi nhật ký. Đây sẽ là hiếm nhưng không nằm ngoài câu hỏi.


2

Hầu như không có sự tuyệt đối nào trong lập trình, để trả lời câu hỏi của bạn, chúng ta cần xem xét một số tính chất mong muốn của các đối tượng và xem liệu đó có phải là thứ gì đó áp dụng cho trường hợp của chúng ta không

  1. Chúng tôi muốn các lớp học có thể dễ dàng kiểm tra.
  2. Chúng tôi muốn các lớp hỗ trợ đồng thời
  3. Chúng tôi muốn các lớp dễ dàng lý luận về bên thứ ba

Nếu chúng tôi trả lời có yo một số trong những câu hỏi này thì có lẽ nên suy nghĩ rất kỹ về các đặc tính và tác dụng phụ của chúng. Tôi cho rằng khi bạn nói tác dụng phụ, bạn có nghĩa là tác dụng phụ thứ cấp kể từ khi cập nhật giá trị là một tác dụng phụ.

Càng nhiều tác dụng phụ nói chung, một lớp sẽ khó kiểm tra hơn. Các tác dụng phụ thứ cấp càng khó xử lý đồng thời, nhưng đó là một vấn đề khó có thể đòi hỏi một số suy nghĩ thiết kế chính.

Gotcha lớn có lẽ dành cho những người coi lớp học của bạn là "hộp đen", sẽ không có sẵn khi một số tiểu bang đã thay đổi chỉ vì họ đã đọc một tài sản hoặc một số tài sản khác thay đổi vì một thay đổi khác (có thể dẫn đến để cập nhật xếp tầng).

Vì vậy, nói chung là không, chúng tôi muốn các thuộc tính dễ lý luận và đơn giản nhất có thể, điều đó có nghĩa là trong quyết định thiết kế nếu không nó sẽ là một phương pháp. Một lập trình viên giỏi luôn có thể phá vỡ các quy tắc, tuy nhiên, hãy chắc chắn rằng bạn có một lý do thực sự tốt :)

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.