Tại sao các phương pháp ma thuật được thực hiện trong C #?


16

Trong C #, tôi bắt đầu thấy tất cả các phương thức ma thuật này xuất hiện mà không được giao diện sao lưu. Tại sao điều này được chọn?

Hãy để tôi giải thích.

Trước đây trong C #, nếu một đối tượng triển khai IEnumerablegiao diện, nó sẽ tự động được lặp lại bởi một foreachvòng lặp. Điều đó có ý nghĩa với tôi, vì nó được hỗ trợ bởi một giao diện, và nếu tôi có Iteratorchức năng riêng của mình trong lớp được lặp đi lặp lại, tôi có thể làm điều đó mà không phải lo lắng rằng nó sẽ có ý nghĩa kỳ diệu khác.

Bây giờ, rõ ràng, (không chắc chắn khi nào), các giao diện này không còn cần thiết nữa. Nó chỉ cần có các chuyển đổi đặt tên đúng.

Một ví dụ khác là làm cho bất kỳ đối tượng nào được chờ đợi bằng cách có một phương thức được đặt tên chính xác GetAwaiter có một vài thuộc tính cụ thể.

Tại sao không tạo một giao diện như họ đã làm với IEnumerablehoặc INotifyPropertyChangedsao lưu "ma thuật" này lên tĩnh?

Thêm chi tiết về những gì tôi muốn nói ở đây:

http://blog.nem.ec/2014/01/01/magic-methods-c-sharp/

Những ưu và nhược điểm của phương pháp ma thuật là gì, và có nơi nào trực tuyến mà tôi có thể tìm thấy bất cứ điều gì về lý do tại sao những quyết định này được đưa ra không?


4
Bạn nên chỉnh sửa ý kiến ​​cá nhân của mình về lý do tại sao "ma thuật là xấu" nếu bạn không muốn bị đóng cửa.
DougM

2
Nếu bạn muốn biết lý do tại sao Anders Hejlsberg làm điều đó, bạn sẽ phải hỏi anh ta. Tôi chỉ có thể cho bạn biết lý do tại sao tôi đã làm điều đó, và nó là để tương thích về phía trước. Các phương thức mở rộng cho phép bạn "giả" thêm các phương thức vào các loại hiện có, nhưng không có giao diện mở rộng. Nếu bạn yêu cầu một giao diện, giả sử, async/ await, thì nó sẽ chỉ hoạt động với mã được viết sau khi .NET 4.5 trở nên phổ biến đủ rộng để trở thành mục tiêu khả thi mà hiện tại về cơ bản. Nhưng một bản dịch cú pháp thuần túy thành các cuộc gọi phương thức cho phép tôi thêm awaitchức năng vào các kiểu hiện có sau khi thực tế.
Jörg W Mittag

2
Lưu ý rằng điều này về cơ bản được áp dụng hướng đối tượng: miễn là một đối tượng phản hồi các thông điệp phù hợp, nó được coi là đúng loại.
Jörg W Mittag

7
"Trước đây" và "bây giờ" của bạn đã lạc hậu - phương pháp ma thuật đã được triển khai như chức năng cơ bản cho một foreachvòng lặp trở lại lúc ban đầu. Có bao giờ đã là một yêu cầu đối với các đối tượng để thực hiện IEnumerablecho foreachđến công việc. Đó chỉ là quy ước để làm như vậy.
Jesse C. Choper

2
@gbulmer đây là nơi tôi nhớ lại nhận ý tưởng từ: blogs.msdn.com/b/ericlippert/archive/2011/06/30/... (và đến một mức độ thấp hơn ericlippert.com/2013/07/22/... )
Jesse C. Choper

Câu trả lời:


16

Nói chung, các phương pháp ma thuật của người dùng được sử dụng khi không thể tạo ra một giao diện hoạt động theo cùng một cách.

Khi foreachđược giới thiệu lần đầu tiên trong C # 1.0 (hành vi đó chắc chắn không có gì gần đây), nó đã phải sử dụng các phương pháp ma thuật, vì không có thuốc generic. Các tùy chọn cơ bản là:

  1. Sử dụng không chung chung IEnumerableIEnumeratorhoạt động với objects, có nghĩa là các loại giá trị đấm bốc. Vì việc lặp đi lặp lại một cái gì đó như một danh sách intnên rất nhanh và chắc chắn không nên tạo ra nhiều giá trị hộp rác, đây không phải là một lựa chọn tốt.

  2. Chờ cho thuốc generic. Điều này có thể có nghĩa là trì hoãn .Net 1.0 (hoặc ít nhất foreach) trong hơn 3 năm.

  3. Sử dụng phương pháp ma thuật.

Vì vậy, họ đã chọn tùy chọn # 3 và nó ở lại với chúng tôi vì lý do tương thích ngược, mặc dù kể từ .Net 2.0, yêu cầu IEnumerable<T>cũng sẽ hoạt động.


Bộ khởi tạo bộ sưu tập có thể trông khác nhau trên mỗi loại bộ sưu tập. So sánh List<T>:

public void Add(T item)

Dictionary<TKey, TValue>:

public void Add(TKey key, TValue value)

Bạn không thể có một giao diện duy nhất chỉ hỗ trợ biểu mẫu đầu tiên List<T>và chỉ giao diện thứ hai Dictionary<TKey, TValue>.


Các phương thức LINQ thường được triển khai như các phương thức mở rộng (do đó có thể chỉ có một triển khai duy nhất ví dụ LINQ to Object cho tất cả các loại triển khai IEnumerable<T>), điều đó có nghĩa là không thể sử dụng giao diện.


Đối với await, GetResult()Phương thức có thể trả về một trong hai voidloại T. Một lần nữa, bạn không thể có một giao diện duy nhất có thể xử lý cả hai. Mặc dù awaitlà một phần dựa trên giao diện: người phục vụ phải thực hiện INotifyCompletionvà cũng có thể thực hiện ICriticalNotifyCompletion.


1
Bạn có chắc chắn "họ đã chọn tùy chọn # 3" cho C # 1.0 không? Hồi ức của tôi là tùy chọn 1. Bộ nhớ của tôi là, C # khẳng định sự vượt trội đáng kể so với Java vì trình biên dịch C # đã 'tự động đấm bốc' và 'tự động mở hộp'. Nó được tuyên bố, C # làm mã như rằng công việc foreach nhiều hơn Java một phần vì lý do đó.
xe cứu thương

1
Tài liệu về foreachC # 1.2 (không có gì trên MSDN cho C # 1.0) nói rằng biểu thức trong foreachEv Evaluates thành một loại thực hiện IEnumerablehoặc một loại khai báo một GetEnumeratorphương thức. Và tôi không chắc ý của bạn là gì, bỏ hộp trong C # luôn rõ ràng.
Svick

1
@gbulmer Và đặc tả ECMA cho C # từ tháng 12 năm 2001 (có nghĩa là nó dành cho C # 1.0) cũng nói rằng (§15.8.4): Một loại C được gọi là loại bộ sưu tập nếu nó thực hiện giao diện System.IEnumerable hoặc thực hiện mô hình bộ sưu tập bằng cách đáp ứng tất cả các tiêu chí sau [Mạnh].
Svick

1
@svick foreach(T x in sequence)áp dụng phôi rõ ràng cho Tcác yếu tố của chuỗi. Vì vậy, nếu chuỗi là một đồng bằng IEnumerableTlà một loại giá trị, nó sẽ bỏ hộp mà không có bất kỳ ép rõ ràng nào được viết trong mã của bạn. Một trong những phần xấu nhất của C #.
CodeInChaos

@CodesInChaos "Tự động mở hộp" nghe có vẻ khá chung chung, tôi không nhận ra nó tham chiếu tính năng cụ thể này. (Tôi đoán là tôi nên có, vì chúng ta đã nói về foreach.)
svick
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.