Ngoại lệ tốt nhất cho đối số loại chung không hợp lệ


106

Tôi hiện đang viết một số mã cho UnconstrainedMelody có các phương thức chung để làm với enums.

Bây giờ, tôi có một lớp tĩnh với một loạt các phương thức chỉ được sử dụng với enum "flags". Tôi không thể thêm điều này làm ràng buộc ... vì vậy có thể chúng cũng sẽ được gọi với các loại enum khác. Trong trường hợp đó, tôi muốn ném một ngoại lệ, nhưng không chắc nên ném cái nào.

Chỉ để làm cụ thể này, nếu tôi có một cái gì đó như thế này:

// Returns a value with all bits set by any values
public static T GetBitMask<T>() where T : struct, IEnumConstraint
{
    if (!IsFlags<T>()) // This method doesn't throw
    {
        throw new ???
    }
    // Normal work here
}

Ngoại lệ tốt nhất để ném là gì? ArgumentExceptionnghe có vẻ hợp lý, nhưng đó là một đối số kiểu chứ không phải là một đối số bình thường, có thể dễ dàng nhầm lẫn mọi thứ. Tôi có nên giới thiệu TypeArgumentExceptionlớp học của riêng mình không? Sử dụng InvalidOperationException? NotSupportedException? Còn gì nữa không?

Tôi muốn thay không tạo ra ngoại lệ của riêng tôi cho điều này trừ khi nó là rõ điều phải làm.


Hôm nay tôi tình cờ gặp điều này khi viết một phương pháp chung trong đó các yêu cầu bổ sung được đặt vào kiểu đang được sử dụng mà không thể mô tả bằng các ràng buộc. Tôi rất ngạc nhiên khi không tìm thấy một loại ngoại lệ đã có trong BCL. Nhưng tình huống khó xử chính xác này là vấn đề mà tôi cũng đã phải đối mặt vài ngày trước trong cùng một dự án cho một sản phẩm chung sẽ chỉ hoạt động với thuộc tính Flags. Ma quái!
Andras Zoltan

Câu trả lời:


46

NotSupportedException nghe có vẻ như nó hoàn toàn phù hợp, nhưng tài liệu nói rõ rằng nó nên được sử dụng cho một mục đích khác. Từ nhận xét của lớp MSDN:

Có những phương thức không được hỗ trợ trong lớp cơ sở, với kỳ vọng rằng những phương thức này sẽ được thực hiện trong các lớp dẫn xuất thay thế. Lớp dẫn xuất chỉ có thể triển khai một tập hợp con của các phương thức từ lớp cơ sở và ném NotSupportedException cho các phương thức không được hỗ trợ.

Tất nhiên, có một cách NotSupportedExceptionrõ ràng là đủ tốt, đặc biệt là với ý nghĩa thông thường của nó. Nói như vậy, tôi không chắc có đúng không.

Với mục đích của Giai điệu không bị ràng buộc ...

Có nhiều điều hữu ích khác nhau có thể được thực hiện với các phương thức / lớp chung trong đó có ràng buộc kiểu "T: enum" hoặc "T: ủy nhiệm" - nhưng thật không may, chúng bị cấm trong C #.

Thư viện tiện ích này hoạt động xung quanh các lệnh cấm sử dụng ildasm / ilasm ...

... có vẻ như một cái mới Exceptioncó thể có thứ tự mặc dù gánh nặng bằng chứng cao mà chúng tôi chỉ cần đáp ứng trước khi tạo tùy chỉnh Exceptions. Một cái gì đó như InvalidTypeParameterExceptioncó thể hữu ích trong toàn bộ thư viện (hoặc có thể không - đây chắc chắn là một trường hợp phức tạp, phải không?).

Khách hàng có cần phân biệt điều này với BCL Exceptions không? Khi nào khách hàng có thể vô tình gọi món này bằng vani enum? Bạn sẽ trả lời các câu hỏi đặt ra như thế nào bởi câu trả lời được chấp nhận cho Những yếu tố nào cần được xem xét khi viết một lớp ngoại lệ tùy chỉnh?


Trên thực tế, việc ném một ngoại lệ chỉ dành cho nội bộ ngay từ đầu gần như rất hấp dẫn, giống như cách mà Code Contracts làm ... Tôi không tin rằng có ai đó nên bắt lấy nó.
Jon Skeet 11/09/09

Thật tệ là nó không thể trả về null!
Jeff Sternal 11/09/09

25
Tôi đang sử dụng TypeArgumentException.
Jon Skeet 11/09/09

Thêm ngoại lệ vào Khung có thể có "gánh nặng bằng chứng" cao, nhưng việc xác định các ngoại lệ tùy chỉnh thì không nên. Những thứ như InvalidOperationExceptionlà kỳ cục, bởi vì "Foo yêu cầu Thanh bộ sưu tập thêm thứ gì đó đã tồn tại, vì vậy Thanh ném IOE" và "Foo yêu cầu Thanh bộ sưu tập thêm một thứ gì đó, vì vậy Bar gọi Boz và ném IOE mặc dù Thanh không mong đợi nó" cả hai sẽ ném cùng một loại ngoại lệ; mã mong đợi để bắt đầu tiên sẽ không mong đợi mã sau. Điều đó đã được nói ...
supercat

... Tôi sẽ nghĩ rằng lập luận ủng hộ một ngoại lệ Framework ở đây thuyết phục hơn là cho một ngoại lệ tùy chỉnh. Bản chất chung của NSE là khi một tham chiếu đến một đối tượng như một loại chung và một số nhưng không phải tất cả các loại đối tượng cụ thể mà các điểm tham chiếu sẽ hỗ trợ một khả năng, cố gắng sử dụng khả năng trên một loại cụ thể mà không không hỗ trợ nó nên ném NSE. Tôi sẽ coi a Foo<T>là một "kiểu chung" và Foo<Bar>là "kiểu cụ thể" trong ngữ cảnh đó, mặc dù không có mối quan hệ "kế thừa" nào giữa chúng.
supercat

24

Tôi sẽ tránh NotSupportedException. Ngoại lệ này được sử dụng trong khuôn khổ nơi một phương pháp không được triển khai và có một thuộc tính chỉ ra rằng loại hoạt động này không được hỗ trợ. Nó không phù hợp ở đây

Tôi nghĩ InvalidOperationException là ngoại lệ thích hợp nhất mà bạn có thể ném vào đây.


Cảm ơn những thông tin cần thiết về NSE. Cũng rất hoan nghênh ý kiến ​​đóng góp từ các đồng nghiệp của bạn, btw ...
Jon Skeet

Vấn đề là, chức năng Jon cần không có gì tương tự trong BCL. Trình biên dịch phải bắt nó. Nếu bạn xóa yêu cầu "thuộc tính" khỏi NotSupportedException, những thứ bạn đã đề cập (như bộ sưu tập ReadOnly) là thứ gần nhất với vấn đề của Jon.
Mehrdad Afshari 11/09/09

Một điểm - Tôi có một phương pháp IsFlags (nó có phải là một phương pháp để được chung chung) là loại của chỉ ra rằng loại hình này hoạt động không được hỗ trợ ... do đó, trong ý nghĩa NSE sẽ là thích hợp. tức là người gọi có thể kiểm tra trước.
Jon Skeet 11/09/09

@Jon: Tôi nghĩ ngay cả khi bạn không có tài sản như vậy nhưng tất cả các thành viên thuộc loại của bạn vốn dĩ dựa vào thực tế Tlà được enumtrang trí bằng Flags, thì sẽ hợp lệ để ném NSE.
Mehrdad Afshari 11/09/09

1
@ Jon: StupidClrExceptionlàm cho một tên vui vẻ;)
Mehrdad Afshari

13

Lập trình chung không nên ném vào thời gian chạy cho các tham số kiểu không hợp lệ. Nó không nên biên dịch, bạn nên thực thi thời gian biên dịch. Tôi không biết những gì IsFlag<T>()chứa, nhưng có lẽ bạn có thể biến điều này thành một thực thi thời gian biên dịch, giống như cố gắng tạo một kiểu chỉ có thể tạo với 'cờ'. Có lẽ một traitslớp học có thể giúp đỡ.

Cập nhật

Nếu bạn phải ném, tôi sẽ bỏ phiếu cho InvalidOperationException. Lý do là các kiểu chung có tham số và lỗi liên quan đến tham số (phương thức) được tập trung xung quanh phân cấp ArgumentException. Tuy nhiên, khuyến nghị về ArgumentException nói rằng

nếu lỗi không liên quan đến chính các đối số, thì nên dùng InvalidOperationException.

Có ít nhất một bước nhảy vọt của niềm tin vào đó, rằng các đề xuất tham số phương pháp cũng được áp dụng cho các tham số chung , nhưng không có gì tốt hơn trong imho phân cấp SystemException.


1
Không, không có cách nào mà điều này có thể bị hạn chế tại thời gian biên dịch. IsFlag<T>xác định liệu enum đã [FlagsAttribute]áp dụng cho nó hay chưa và CLR không có các ràng buộc dựa trên các thuộc tính. Nó sẽ trong một thế giới hoàn hảo - hoặc có muốn có một số cách khác để hạn chế nó - nhưng trong trường hợp này nó chỉ cần không làm việc :(
Jon Skeet

(+1 cho nguyên tắc chung - tôi rất muốn có thể hạn chế nó.)
Jon Skeet 11/09/09

9

Tôi sẽ sử dụng NotSupportedException vì đó là những gì bạn đang nói. Các enums khác với những cái cụ thể không được hỗ trợ . Điều này tất nhiên sẽ được nêu rõ hơn trong thông báo ngoại lệ.


2
NotSupportedException được sử dụng cho một mục đích rất khác trong BCL. Nó không phù hợp ở đây. blogs.msdn.com/jaredpar/archive/2008/12/12/…
JaredPar 11/09/09

8

Tôi sẽ đi cùng NotSupportedException. Mặc dù có ArgumentExceptionvẻ ổn, nhưng nó thực sự được mong đợi khi một đối số được truyền cho một phương thức là không thể chấp nhận được. Đối số kiểu là một đặc điểm xác định cho phương thức thực mà bạn muốn gọi, không phải là một "đối số" thực. InvalidOperationExceptionnên được ném khi thao tác bạn đang thực hiện có thể hợp lệ trong một số trường hợp nhưng đối với tình huống cụ thể, nó không thể chấp nhận được.

NotSupportedExceptionđược ném khi một hoạt động vốn dĩ không được hỗ trợ. Ví dụ, khi triển khai một giao diện mà một thành viên cụ thể không có ý nghĩa đối với một lớp. Điều này giống như một tình huống tương tự.


Ừm. Nó vẫn không khá cảm thấy đúng, nhưng tôi nghĩ rằng nó sẽ là điều gần gũi nhất với nó.
Jon Skeet 11/09/09

Jon: cảm thấy không ổn vì chúng tôi tự nhiên mong đợi nó được trình biên dịch bắt kịp.
Mehrdad Afshari 11/09/09

Đúng vậy. Đây là một loại kỳ lạ của chế mà tôi muốn được áp dụng nhưng không thể :)
Jon Skeet

6

Rõ ràng, Microsoft sử dụng ArgumentExceptioncho điều đó, như được minh họa trong ví dụ về Expression.Lambda <> , Enum.TryParse <> hoặc Marshal.GetDelegateForFunctionPointer <> trong phần Exceptions. Tôi cũng không thể tìm thấy bất kỳ ví dụ nào cho biết khác (mặc dù đã tìm kiếm nguồn tham chiếu cục bộ cho TDelegateTEnum).

Vì vậy, tôi nghĩ rằng thật an toàn khi giả định rằng ít nhất trong mã Microsoft, một thực tế phổ biến là sử dụng ArgumentExceptioncho các đối số kiểu chung không hợp lệ ngoài các đối số biến cơ bản. Cho rằng mô tả ngoại lệ trong tài liệu không phân biệt giữa những tài liệu đó, nó cũng không quá căng.

Hy vọng rằng nó quyết định câu hỏi một lần và mãi mãi.


Một ví dụ duy nhất trong khuôn khổ là không đủ đối với tôi, không - với số lượng vị trí mà tôi nghĩ MS đã lựa chọn sai trong các trường hợp khác :) Tôi sẽ không lấy TypeArgumentExceptiontừ đó ArgumentException, đơn giản vì đối số kiểu không phải là một tranh luận.
Jon Skeet

1
Điều đó chắc chắn hấp dẫn hơn về mặt "đó là những gì MS luôn làm". Nó không làm cho nó trở nên hấp dẫn hơn khi so sánh với tài liệu ... và tôi biết có rất nhiều người trong nhóm C # quan tâm sâu sắc đến sự khác biệt giữa đối số thông thường và đối số kiểu :) Nhưng cảm ơn vì các ví dụ - chúng rất hữu ích.
Jon Skeet

@Jon Skeet: Đã chỉnh sửa; bây giờ nó bao gồm 3 ví dụ từ các thư viện MS khác nhau, tất cả đều có ArgumentException được ghi lại dưới dạng tài liệu được ném ra; vì vậy nếu đó là một lựa chọn tồi, ít nhất nó cũng là một lựa chọn kém nhất quán. ;) Tôi đoán Microsoft giả định rằng đối số thông thường và đối số kiểu đều là đối số; và cá nhân tôi cho rằng giả định như vậy là khá hợp lý. ^^ '
Alice

À, đừng bận tâm, có vẻ như bạn đã nhận thấy điều đó rồi. Rất vui vì tôi có thể giúp. ^^
Alice

Tôi nghĩ chúng ta sẽ phải đồng ý để không đồng ý về việc liệu có hợp lý để đối xử với họ như nhau hay không. Chúng chắc chắn không giống nhau khi nói đến phản xạ, hoặc các quy tắc ngôn ngữ, v.v ... chúng được xử lý rất khác nhau.
Jon Skeet


2

Việc ném một ngoại lệ được thực hiện tùy chỉnh nên luôn được thực hiện trong bất kỳ trường hợp nào có nghi vấn. Một ngoại lệ tùy chỉnh sẽ luôn hoạt động, bất kể người dùng API cần gì. Nhà phát triển có thể bắt một trong hai loại ngoại lệ nếu anh ta không quan tâm, nhưng nếu nhà phát triển cần xử lý đặc biệt, anh ta sẽ là SOL.


Ngoài ra, nhà phát triển nên ghi lại tất cả các ngoại lệ được đưa ra trong các nhận xét XML.
Eric Schneider

1

Làm thế nào về việc kế thừa từ NotSupportedException. Mặc dù tôi đồng ý với @Mehrdad rằng nó có ý nghĩa nhất, nhưng tôi nghe ý kiến ​​của bạn rằng nó có vẻ không phù hợp một cách hoàn hảo. Vì vậy, kế thừa từ NotSupportedException và theo cách đó những người viết mã dựa trên API của bạn vẫn có thể bắt được NotSupportedException.


1

Tôi luôn cảnh giác với việc viết các ngoại lệ tùy chỉnh, hoàn toàn với lý do chúng không phải lúc nào cũng được ghi chép rõ ràng và gây nhầm lẫn nếu không được đặt tên chính xác.

Trong trường hợp này, tôi sẽ ném một ArgumentException cho lỗi kiểm tra cờ. Nó thực sự phụ thuộc vào sở thích. Một số tiêu chuẩn mã hóa mà tôi đã thấy đi xa hơn để xác định loại ngoại lệ nào nên được đưa vào các tình huống như thế này.

Nếu người dùng đang cố gắng chuyển một thứ gì đó không phải là enum thì tôi sẽ ném ra một lỗi không hợp lệ (InvalidOperationException).

Biên tập:

Những người khác nêu ra một điểm thú vị rằng điều này không được hỗ trợ. Mối quan tâm duy nhất của tôi với NotSupportedException nói chung đó là những ngoại lệ được ném ra khi "vật chất tối" được đưa vào hệ thống, hay nói một cách khác, "Phương pháp này phải đi vào hệ thống trên giao diện này, nhưng chúng tôi đã thắng không bật nó cho đến phiên bản 2.4 "

Tôi cũng đã thấy NotSupportedExceptions được ném như một ngoại lệ cấp phép "bạn đang chạy phiên bản miễn phí của phần mềm này, chức năng này không được hỗ trợ".

Chỉnh sửa 2:

Một cái khác có thể:

System.ComponentModel.InvalidEnumArgumentException  

Ngoại lệ được ném ra khi sử dụng các đối số không hợp lệ là kiểu liệt kê.


Tôi sẽ có nó bị hạn chế là một enum (sau một số pokery jiggery) - đó chỉ là những lá cờ mà tôi lo lắng.
Jon Skeet

Tôi nghĩ những kẻ cấp phép đó nên ném một phiên bản của một LicensingExceptionlớp kế thừa từ InvalidOperationException.
Mehrdad Afshari 11/09/09

Tôi đồng ý với Mehrdad, Rất tiếc, các trường hợp ngoại lệ là một trong những khu vực có rất nhiều màu xám trong khuôn khổ. Nhưng tôi chắc rằng điều này giống nhau đối với nhiều ngôn ngữ. (không nói rằng tôi sẽ quay lại lỗi thời gian chạy của vb6 13 hehe)
Peter

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.