Найкращий виняток для недійсного аргументу загального типу


106

Я зараз пишу код для UnconstrainedMelody, який має загальні методи, що стосуються перерахунків.

Тепер у мене є статичний клас із купою методів, які призначені лише для перерахувань "прапорів". Я не можу додати це як обмеження ... тому можливо, що вони будуть викликані і з іншими типами перерахунків. У такому випадку я хотів би кинути виняток, але я не впевнений, який саме кинути.

Просто щоб зробити це конкретніше, якщо у мене є щось подібне:

// 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
}

Який найкращий виняток кинути? ArgumentExceptionзвучить логічно, але це аргумент типу, а не звичайний аргумент, який може легко заплутати речі. Чи варто ввести власний TypeArgumentExceptionклас? Використовувати InvalidOperationException? NotSupportedException? Щось іще?

Я б краще не створив для цього власного винятку, якщо явно це не правильно.


Сьогодні я наткнувся на це, написавши загальний метод, коли додаткові вимоги ставляться до використовуваного типу, який неможливо описати з обмеженнями. Я був здивований, що не знайшов тип винятку вже в BCL. Але ця точна дилема була одна з якою я також стикався кілька днів тому в тому самому проекті для загального, який працюватиме лише з атрибутом Flags. Моторошний!
Андрас Золтан

Відповіді:


46

NotSupportedException звучить так, як це прямо підходить, але в документації чітко зазначено, що його слід використовувати для інших цілей. З зауважень класу MSDN:

Існують методи, які не підтримуються в базовому класі, сподіваючись, що ці методи будуть реалізовані замість похідних класів. Похідний клас може реалізувати лише підмножину методів із базового класу та викидати NotSupportedException для непідтримуваних методів.

Звичайно, існує спосіб, який NotSupportedException, очевидно, досить хороший, особливо з огляду на його здоровий глузд. Сказавши це, я не впевнений, чи правильно це.

Враховуючи мету Нестриманої мелодії ...

Є різні корисні речі, які можна зробити за допомогою загальних методів / класів, де є обмеження типу "T: enum" або "T: delegate" - але, на жаль, вони заборонені в C #.

Ця утиліта працює над заборонами використання ildasm / ilasm ...

... здається, що нове Exceptionможе бути в порядку, незважаючи на високий тягар доказів, з яким нам справедливо доводиться зустрічатися, перш ніж створювати замовлення Exceptions. Щось подібне InvalidTypeParameterExceptionможе бути корисним у всій бібліотеці (а може й ні - це, безумовно, кращий випадок, правда?).

Чи повинні клієнти мати можливість відрізняти це від виключень BCL? Коли клієнт може випадково зателефонувати цьому за допомогою ванілі enum? Як би ви відповіли на запитання, що поставлені прийнятою відповіддю на те, Які фактори слід враховувати при написанні спеціального класу винятків?


Насправді майже спокусливо в першу чергу викинути виключно внутрішній виняток, так само, як це роблять Code Contracts ... Я не вірю, що хтось повинен це вловлювати.
Джон Скіт

Шкода, що він не може просто повернути нуль!
Jeff Sternal

25
Я збираюся з TypeArgumentException.
Джон Скіт

Додавання винятків до Рамок може мати високий "тягар доказування", але визначення спеціальних винятків не повинно. Такі речі InvalidOperationExceptionбувають прискіпливими, тому що "Foo просить Bar Bar Collection додати щось, що вже існує, тому Bar кидає IOE" і "Foo просить Bar Collection Bar щось додати, тому Bar називає Boz, який кидає IOE, навіть якщо Bar не очікує цього" обидва будуть кидати один і той же тип виключення; код, який очікує спіймання першого, не очікує останнього. Це було сказано ...
суперкат

... Я думаю, що аргумент на користь винятку Framework тут є більш переконливим, ніж для спеціального винятку. Загальний характер NSE полягає в тому, що коли посилання на об'єкт як загальний тип, а деякі, але не всі конкретні типи об'єктів, на які опорні точки підтримуватимуть здатність, намагаються використовувати здатність для певного типу, який не Не підтримую, це повинно кинути NSE. Я вважав Foo<T>би a "загальним типом" і Foo<Bar>"конкретним типом" у цьому контексті, хоча між ними немає "спадкового" відношення.
supercat

24

Я б уникнув NotSupportedException. Цей виняток використовується в рамках, коли метод не реалізований і є властивість, що вказує на те, що цей тип операції не підтримується. Тут не підходить

Я думаю, що InvalidOperationException - це найбільш підходящий виняток, який можна сюди кинути.


Дякуємо за підсумки щодо НФБ. Будемо раді також внести ваші колеги, btw ...
Джон Скіт

Справа в тому, що функціонал, який потребує Джон, не має нічого подібного в BCL. Компілятор повинен його зловити. Якщо ви видалите вимогу "властивості" з NotSupportedException, ви згадали, що ви згадали (наприклад, колекція ReadOnly), що є найближчим до проблеми Джона.
Мехрдад Афшарі

Один момент - у мене є метод IsFlags (він повинен бути методом, який має бути загальним), який є таким чином , що свідчить про те, що цей тип операцій не підтримується ... так що в цьому сенсі NSE було б доречним. тобто абонент може перевірити спочатку.
Джон Скіт

@Jon: Я думаю, навіть якщо у вас немає такого майна, але всі члени вашого типу по суті покладаються на те, щоT є enumприкрашено Flags, це було б справедливо , щоб кинути NSE.
Мехрдад Афшарі

1
@Jon: StupidClrExceptionробить веселе ім’я;)
Мехрдад Афшарі

13

Загальне програмування не повинно запускати час виконання для недійсних параметрів типу. Він не повинен компілювати, у вас має бути час виконання компіляції. Я не знаю, що IsFlag<T>()містить, але, можливо, ви можете перетворити це на виконання часу компіляції, наприклад, намагаючись створити тип, який можна створити лише за допомогою "прапорів". Можливо, traitsклас може допомогти.

Оновлення

Якщо ви повинні кинути, я би проголосував за InvalidOperationException. Аргументація полягає в тому, що загальні типи мають параметри, а помилки, пов'язані з (методом) параметрами, зосереджені навколо ієрархії ArgumentException. Однак у рекомендації щодо ArgumentException зазначено, що

якщо відмова не включає самих аргументів, слід використовувати InvalidOperationException.

У цьому методі є хоча б один стрибок віри рекомендації щодо параметрів також слід застосовувати до загальних параметрів, але нічого кращого в ієрархії системи SystemException немає.


1
Ні, немає ніякого способу це обмежити під час компіляції. IsFlag<T>визначає, чи [FlagsAttribute]застосував до нього enum , а CLR не має обмежень на основі атрибутів. Це було б у досконалому світі - або був би якийсь інший спосіб його обмежити - але в цьому випадку це просто не працює :(
Джон Скіт,

(+1 для загального принципу , хоча - я хотів би бути в змозі . Стримувати його)
Джон Скит

9

Я б використовував NotSupportedException як саме те, що ви говорите. Інші перерахунки, ніж специфічні, не підтримуються . Це, звичайно, буде викладено чіткіше у повідомленні про виключення.


2
NotSupportedException використовується для зовсім іншої мети в BCL. Тут не підходить. blogs.msdn.com/jaredpar/archive/2008/12/12/…
JaredPar

8

Я б пішов з NotSupportedException. Хоча це ArgumentExceptionвиглядає чудово, але насправді очікується, коли аргумент, переданий методу, неприйнятний. Аргумент типу - визначальна характеристика для фактичного методу, який ви хочете викликати, а не реальний "аргумент".InvalidOperationExceptionви повинні бути кинуті, коли операція, яку ви виконуєте, може бути дійсною в деяких випадках, але для конкретної ситуації це неприпустимо.

NotSupportedExceptionкидається, коли операція по суті не підтримується. Наприклад, при реалізації інтерфейсу, коли певний член не має сенсу для класу. Це схоже на подібну ситуацію.


Ммм. Це все ще не зовсім добре, але я думаю, що це буде найближче до цього.
Джон Скіт

Джон: це не вірно, тому що ми, звичайно, очікуємо, що компілятор його схопить.
Мехрдад Афшарі

Так. Це дивне обмеження, яке я хотів би застосувати, але не можу :)
Jon Skeet

6

Очевидно, Microsoft використовує ArgumentExceptionдля цього, як показано на прикладі Expression.Lambda <> , Enum.TryParse <> або Marshal.GetDelegateForFunctionPointer <> у розділі Винятки. Я не зміг знайти жодного прикладу, який би вказував інакше (незважаючи на пошук локального довідкового джерела для TDelegateта TEnum).

Отже, я вважаю, що можна припустити, що принаймні в коді Microsoft це звичайна практика використання ArgumentExceptionдля недійсних аргументів загального типу, окрім основних змінних. Враховуючи, що опис винятку в документах не розмежовує їх, також це не так вже й велика розтяжність.

Сподіваємось, це вирішує питання питання раз і назавжди.


Не одного прикладу в рамках мало для мене, немає - враховуючи кількість місць , де я думаю , що MS зробила поганий вибір в інших випадках :) я б не витягти TypeArgumentExceptionз ArgumentException, просто тому, що аргумент типу не є регулярним аргумент.
Джон Скіт

1
Це, безумовно, більш переконливо з точки зору "це те, що МС робить послідовно". Це не робить його більш переконливим з точки зору відповідності документації ... і я знаю, що в команді C # є багато людей, які глибоко дбають про різницю між регулярними аргументами та аргументами типу :) Але дякую за приклади - вони дуже корисні.
Джон Скіт

@Jon Skeet: зробив правки; тепер він включає 3 приклади з різних бібліотек MS, усі з ArgumentException задокументовані як кинуті; тож якщо це поганий вибір, принаймні це послідовний поганий вибір. ;) Я здогадуюсь, Microsoft вважає, що аргументи та аргументи типу - це обидва аргументи; і особисто я вважаю таке припущення цілком обґрунтованим. ^^ '
Аліса

А, ніколи не маю на увазі, здається, ви це вже помітили. Радий, що можу допомогти. ^^
Аліса

Я думаю, що нам доведеться погодитися не погоджуватися, чи розумно ставитися до них однаково. Вони, звичайно, не однакові, коли мова йде про роздуми, мовні правила тощо ... з ними поводиться дуже по- різному.
Джон Скіт


2

Викидання на замовлення виключення завжди слід робити в будь-якому випадку, коли це сумнівно. Спеціальний виняток завжди працюватиме, незалежно від потреб користувачів API. Розробник може зловити будь-який тип винятку, якщо він не піклується, але якщо розробнику потрібна спеціальна обробка, він буде SOL.


Також розробник повинен задокументувати всі винятки, викладені в коментарях XML.
Ерік Шнайдер

1

Як щодо наслідування від NotSupportedException. Хоча я погоджуюся з @Mehrdad, що це має найбільше сенс, я чую вашу думку, що це, здається, не ідеально підходить. Тож успадковуйте від NotSupportedException, і таким чином люди, що кодують ваш API, можуть все-таки зловити NotSupportedException.


1

Я завжди насторожено пишу власні винятки, виключно на тій підставі, що вони не завжди чітко зафіксовані та створюють плутанину, якщо не названі правильно.

У цьому випадку я б кинув ArgumentException за помилку перевірки прапорів. Все дійсно до уподобань. Деякі стандарти кодування, які я бачив, йдуть далеко до того, щоб визначити, які типи винятків слід кидати в таких сценаріях.

Якщо користувач намагався передати щось, що не було перерахуванням, я б кинув InvalidOperationException.

Редагувати:

Інші піднімають цікавий момент, що це не підтримується. Моє єдине занепокоєння щодо NotSupportedException - це те, що, як правило, це винятки, які викидаються, коли "темна матерія" була введена в систему, або, кажучи іншим способом, "Цей метод повинен зайти в систему на цьому інтерфейсі, але ми виграли не вмикайте його, поки версія 2.4 "

Я також бачив, що NotSupportedExceptions кидається як виняток з ліцензування "Ви використовуєте безкоштовну версію цього програмного забезпечення, ця функція не підтримується".

Редагувати 2:

Інший можливий варіант:

System.ComponentModel.InvalidEnumArgumentException  

Виняток, викинутий при використанні недійсних аргументів, які є перелічниками.


У мене це буде обмежено переживати (після деякого джиггерингу покеру) - про мене хвилюються лише ті прапори.
Джон Скіт

Я думаю, що ті хлопці, які надають ліцензії, повинні кинути екземпляр LicensingExceptionкласу, який успадковується InvalidOperationException.
Мехрдад Афшарі

Я погоджуюся з Мехрдадом, винятки - це, на жаль, одна з тих областей, де в рамках є багато сірого. Але я впевнений, що це так само для багатьох мов. (не кажу, що я повернусь до помилки виконання vb6 13 хе-хе)
Пітер,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.