Лучшее исключение для недопустимого аргумента универсального типа

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 ... Я не думаю, что кто-то должен это замечать.
Джон Скит,
Жаль, что он не может просто вернуть null!
Джефф Стернал,
25
Я использую TypeArgumentException.
Джон Скит,
Добавление исключений в Framework может иметь большое «бремя доказательства», а определение пользовательских исключений - нет. Такие вещи, как InvalidOperationExceptionнеприятные, потому что «Foo просит коллекцию Bar добавить что-то, что уже существует, поэтому Bar бросает IOE» и «Foo просит коллекцию Bar добавить что-то, поэтому Bar вызывает Boz, который бросает IOE, даже если Bar не ожидает этого» оба будут генерировать один и тот же тип исключения; код, который ожидает поймать первое, не будет ожидать второго. Как было сказано ...
supercat
... Я думаю, что аргумент в пользу исключения Framework здесь более убедителен, чем для пользовательского исключения. Общая природа NSE заключается в том, что при ссылке на объект как на общий тип и на некоторые, но не на все конкретные типы объектов, для которых опорные точки будут поддерживать способность, предпринимается попытка использовать способность для определенного типа, который не Не поддерживаю это надо кинуть NSE. Я бы рассматривал a Foo<T>как «общий тип» и Foo<Bar>как «особый тип» в этом контексте, даже если между ними нет отношения «наследования».
supercat 06
25

Я бы избегал NotSupportedException. Это исключение используется в среде, где метод не реализован и есть свойство, указывающее, что этот тип операции не поддерживается. Здесь не подходит

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

ДжаредПар
источник
Спасибо за предупреждение о NSE. Между прочим, я бы хотел услышать мнение ваших коллег ...
Джон Скит,
Дело в том, что функциональность, которая нужна Джону, не имеет ничего подобного в BCL. Компилятор должен это уловить. Если вы удалите требование «свойства» из NotSupportedException, вещи, которые вы упомянули (например, коллекция ReadOnly), будут наиболее близки к проблеме Джона.
Mehrdad Afshari,
Один момент - у меня есть метод IsFlags (он должен быть универсальным), который вроде как указывает на то, что этот тип операции не поддерживается ... так что в этом смысле NSE будет подходящим. т.е. вызывающий может сначала проверить.
Джон Скит,
@Jon: Я думаю, что даже если у вас нет такого свойства, но все члены вашего типа по своей сути полагаются на тот факт, что Tон enumукрашен Flags, было бы правильно выбросить NSE.
Mehrdad Afshari,
1
@Jon: StupidClrExceptionделает забавное имя;)
Мехрдад Афшари
13

Универсальное программирование не должно вызывать во время выполнения недопустимые параметры типа. Он не должен компилироваться, у вас должно быть принудительное исполнение во время компиляции. Я не знаю, что IsFlag<T>()содержит, но, возможно, вы можете превратить это в принудительное выполнение времени компиляции, например, при попытке создать тип, который можно создать только с помощью «флагов». Возможно, traitsкласс может помочь.

Обновить

Если вы должны выбросить, я бы проголосовал за InvalidOperationException. Причина в том, что универсальные типы имеют параметры, а ошибки, связанные с параметрами (метода), сосредоточены вокруг иерархии ArgumentException. Однако в рекомендации по ArgumentException указано, что

если сбой не связан с самими аргументами, следует использовать InvalidOperationException.

Есть, по крайней мере, один прыжок веры, рекомендации по параметрам метода также должны применяться к общим параметрам, но в иерархии SystemException, imho, нет ничего лучше.

Ремус Русану
источник
1
Нет, это никак нельзя ограничить во время компиляции. IsFlag<T>определяет, применено ли к нему перечисление [FlagsAttribute], и CLR не имеет ограничений на основе атрибутов. Это было бы в идеальном мире - или был бы другой способ ограничить это - но в этом случае это просто не работает :(
Джон Скит
(+1 за общий принцип - я бы хотел иметь возможность ограничить его.)
Джон Скит,
9

Я бы использовал NotSupportedException, как вы говорите. Другие перечисления, кроме определенных, не поддерживаются . Это, конечно, будет более четко указано в сообщении об исключении.

Роббан
источник
2
NotSupportedException используется в BCL для совершенно другой цели. Здесь это не подходит. blogs.msdn.com/jaredpar/archive/2008/12/12/…
JaredPar
8

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

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

Мехрдад Афшари
источник
Ммм. Это все еще кажется не совсем правильным, но я думаю, что это будет ближе всего к этому.
Джон Скит,
Джон: Это кажется неправильным, потому что мы, естественно, ожидаем, что компилятор поймает это.
Mehrdad Afshari,
Ага. Это странное ограничение, которое я бы хотел применить, но не могу :)
Джон Скит,
6

Судя по всему, Microsoft использует ArgumentExceptionдля этого, как показано на примере Expression.Lambda <> , Enum.TryParse <> или Marshal.GetDelegateForFunctionPointer <> в разделе «Исключения». Я не смог найти ни одного примера, указывающего на иное (несмотря на поиск в местном справочном источнике для TDelegateи TEnum).

Итак, я думаю, что можно с уверенностью предположить, что, по крайней мере, в коде Microsoft это обычная практика для использования ArgumentExceptionнедопустимых аргументов универсального типа, помимо основных переменных. Учитывая, что описание исключения в документах не делает различий между ними, это тоже не слишком большая натяжка.

Надеюсь, это решит вопрос раз и навсегда.

Алиса
источник
Мне не хватает одного примера во фреймворке, нет - учитывая количество мест, где, я думаю, MS сделала плохой выбор в других случаях :) Я бы не стал выводить TypeArgumentExceptionиз него ArgumentExceptionпросто потому, что аргумент типа не является обычным аргумент.
Джон Скит,
1
Это, безусловно, более убедительно с точки зрения того, «что MS делает постоянно». Это не делает его более убедительным с точки зрения соответствия документации ... и я знаю, что в команде C # есть множество людей, которым глубоко небезразлична разница между обычными аргументами и аргументами типа :) Но спасибо за примеры - они очень полезны.
Джон Скит,
@Jon Skeet: внес правку; теперь он включает 3 примера из разных библиотек MS, все с ArgumentException, задокументированным как выброшенное; так что, если это плохой выбор, по крайней мере, это постоянный плохой выбор. ;) Я полагаю, Microsoft предполагает, что обычные аргументы и аргументы типа являются аргументами; и лично я считаю такое предположение вполне разумным. ^^ '
Алиса
Да ладно, кажется, ты это уже заметил. Рад, что смог помочь. ^^
Алиса
Думаю, нам придется согласиться, чтобы не соглашаться, разумно ли относиться к ним так же. Они , конечно , не то же самое , когда дело доходит до отражения или правил языка, и т.д ... они обрабатываются очень по- разному.
Джон Скит,
3

Я бы пошел с NotSupportedExpcetion.

Карл Бергквист
источник
2

Выбрасывание индивидуализированного исключения всегда должно выполняться в любом случае, когда это вызывает сомнения. Пользовательское исключение будет работать всегда, независимо от потребностей пользователей API. Разработчик может поймать любой тип исключения, если ему все равно, но если разработчику требуется особая обработка, он будет SOL.

Эрик Шнайдер
источник
Также разработчик должен задокументировать все исключения, возникающие в комментариях XML.
Эрик Шнайдер,
1

Как насчет наследования NotSupportedException. Хотя я согласен с @Mehrdad в том, что это имеет наибольший смысл, я слышал вашу точку зрения, что это, похоже, не идеально подходит. Поэтому наследуйте NotSupportedException, и таким образом люди, кодирующие ваш API, все равно могут перехватить NotSupportedException.

BFree
источник
1

Я всегда с осторожностью отношусь к написанию пользовательских исключений исключительно на том основании, что они не всегда четко документируются и вызывают путаницу, если не названы правильно.

В этом случае я бы выбросил исключение ArgumentException при ошибке проверки флагов. На самом деле все зависит от предпочтений. Некоторые стандарты кодирования, которые я видел, доходят до того, что определяют, какие типы исключений следует создавать в подобных сценариях.

Если пользователь пытался передать что-то, что не было перечислением, я бы выбросил InvalidOperationException.

Редактировать:

Остальные поднимают интересный вопрос, что это не поддерживается. Меня беспокоит только NotSupportedException, что обычно это исключения, которые возникают, когда в систему вводится «темная материя», или, другими словами, «Этот метод должен входить в систему через этот интерфейс, но мы выиграли не включаю до версии 2.4 "

Я также видел, как NotSupportedExceptions генерировалось как исключение лицензирования: «вы используете бесплатную версию этого программного обеспечения, эта функция не поддерживается».

Изменить 2:

Другой возможный:

System.ComponentModel.InvalidEnumArgumentException  

Исключение, возникающее при использовании недопустимых аргументов, которые являются перечислителями.

Питер
источник
Я ограничу его перечислением (после некоторого ухищрения) - меня беспокоят только флаги.
Джон Скит,
Я думаю, что этим лицензирующим парням следует создать экземпляр LicensingExceptionкласса, унаследованного от InvalidOperationException.
Mehrdad Afshari,
Я согласен, Mehrdad, исключения - это, к сожалению, одна из тех областей, где в структуре много серого. Но я уверен, что это то же самое для многих языков. (не говоря, что я вернусь к ошибке времени выполнения vb6 13, хе-хе)
Питер,