La mejor excepción para un argumento de tipo genérico no válido

106

Actualmente estoy escribiendo un código para UnconstrainedMelody que tiene métodos genéricos relacionados con enumeraciones.

Ahora, tengo una clase estática con un montón de métodos que solo deben usarse con enumeraciones "flags". No puedo agregar esto como una restricción ... por lo que es posible que también se llamen con otros tipos de enumeración. En ese caso, me gustaría lanzar una excepción, pero no estoy seguro de cuál lanzar.

Solo para hacer esto concreto, si tengo algo como esto:

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

¿Cuál es la mejor excepción para lanzar? ArgumentExceptionsuena lógico, pero es un argumento de tipo en lugar de un argumento normal, que podría confundir fácilmente las cosas. ¿Debo presentar mi propia TypeArgumentExceptionclase? Uso InvalidOperationException? NotSupportedException? ¿Algo más?

Yo más bien no crear mi propia excepción para esto a menos que sea claramente lo que hay que hacer.

Jon Skeet
fuente
Me encontré con esto hoy al escribir un método genérico donde se colocan requisitos adicionales en el tipo que se usa que no se puede describir con restricciones. Me sorprendió no encontrar un tipo de excepción en la BCL. Pero este dilema exacto fue uno que también enfrenté hace unos días en el mismo proyecto para un genérico que solo funcionará con un atributo Flags. ¡Escalofriante!
Andras Zoltan

Respuestas:

46

NotSupportedException Parece que encaja perfectamente, pero la documentación establece claramente que debe usarse para un propósito diferente. De los comentarios de la clase de MSDN:

Hay métodos que no son compatibles con la clase base, con la expectativa de que estos métodos se implementen en las clases derivadas. La clase derivada podría implementar solo un subconjunto de los métodos de la clase base y lanzar NotSupportedException para los métodos no admitidos.

Por supuesto, hay una forma en la que NotSupportedExceptionobviamente es suficientemente buena, especialmente dado su significado de sentido común. Habiendo dicho eso, no estoy seguro de si está bien.

Dado el propósito de Melodía sin restricciones ...

Hay varias cosas útiles que se pueden hacer con métodos / clases genéricos donde hay una restricción de tipo de "T: enum" o "T: delegate", pero desafortunadamente, están prohibidas en C #.

Esta biblioteca de utilidades trabaja alrededor de las prohibiciones usando ildasm / ilasm ...

... parece que una nueva Exceptionpodría estar en orden a pesar de la gran carga de pruebas que tenemos que cumplir antes de crear una costumbre Exceptions. Algo como InvalidTypeParameterExceptionpodría ser útil en toda la biblioteca (o tal vez no; este es seguramente un caso marginal, ¿verdad?).

¿Los clientes deberán poder distinguir esto de las excepciones BCL? ¿Cuándo podría un cliente llamar accidentalmente a esto usando una vainilla enum? ¿Cómo respondería las preguntas planteadas por la respuesta aceptada a Qué factores deben tenerse en cuenta al escribir una clase de excepción personalizada?

Jeff Sternal
fuente
De hecho, es casi tentador lanzar una excepción solo interna en primer lugar, de la misma manera que lo hace Code Contracts ... No creo que nadie deba detectarlo.
Jon Skeet
¡Lástima que no pueda devolver nulo!
Jeff Sternal
25
Voy con TypeArgumentException.
Jon Skeet
Agregar excepciones al Framework puede tener una gran "carga de la prueba", pero la definición de excepciones personalizadas no debería. Cosas como InvalidOperationExceptionson asquerosas, porque "Foo le pide a Collection Bar que agregue algo que ya existe, entonces Bar lanza IOE" y "Foo le pide a Collection Bar que agregue algo, por lo que Bar llama a Boz, que lanza IOE aunque Bar no lo espera" ambos arrojarán el mismo tipo de excepción; el código que espera captar el primero no espera el segundo. Habiendo dicho eso ...
supercat
... Creo que el argumento a favor de una excepción de Framework aquí es más convincente que el de una excepción personalizada. La naturaleza general de NSE es que cuando se hace una referencia a un objeto como tipo general, y algunos pero no todos los tipos específicos de objetos para los cuales los puntos de referencia apoyarán una habilidad, se intenta usar la habilidad en un tipo específico que no 't apoyarlo debería lanzar NSE. Consideraría que un Foo<T>es un "tipo general" y Foo<Bar>un "tipo específico" en ese contexto, aunque no hay una relación de "herencia" entre ellos.
supercat
24

Evitaría NotSupportedException. Esta excepción se utiliza en el marco donde no se implementa un método y hay una propiedad que indica que este tipo de operación no es compatible. No encaja aqui

Creo que InvalidOperationException es la excepción más apropiada que podría lanzar aquí.

JaredPar
fuente
Gracias por el aviso sobre NSE. También agradecería las aportaciones de sus colegas, por cierto ...
Jon Skeet
El caso es que la funcionalidad que necesita Jon no tiene nada parecido en la BCL. Se supone que el compilador lo detecta. Si elimina el requisito de "propiedad" de NotSupportedException, las cosas que mencionó (como la colección ReadOnly) son lo más parecido al problema de Jon.
Mehrdad Afshari
Un punto - Tengo un método IsFlags (tiene que ser un método a ser genéricos), que es una especie de indicar que este tipo de operación no es compatible ... así que en ese sentido NSE sería apropiado. es decir, la persona que llama puede comprobar primero.
Jon Skeet
@Jon: Creo que incluso si no tiene esa propiedad, pero todos los miembros de su tipo confían inherentemente en el hecho de que Testá enumdecorado con Flags, sería válido lanzar NSE.
Mehrdad Afshari
1
@Jon: StupidClrExceptionhace un nombre divertido;)
Mehrdad Afshari
13

La programación genérica no debe lanzarse en tiempo de ejecución para parámetros de tipo no válidos. No debería compilarse, debería tener una aplicación de tiempo de compilación. No sé qué IsFlag<T>()contiene, pero tal vez pueda convertir esto en una aplicación de tiempo de compilación, como intentar crear un tipo que solo es posible crear con 'banderas'. Quizás una traitsclase pueda ayudar.

Actualizar

Si debe lanzar, votaría por InvalidOperationException. El razonamiento es que los tipos genéricos tienen parámetros y los errores relacionados con los parámetros (métodos) se centran en la jerarquía ArgumentException. Sin embargo, la recomendación sobre ArgumentException establece que

si la falla no involucra los argumentos en sí, entonces se debe usar InvalidOperationException.

Hay al menos un acto de fe allí, que las recomendaciones de parámetros de método también se deben aplicar a parámetros genéricos , pero no hay nada mejor en la jerarquía de SystemException en mi humilde opinión.

Remus Rusanu
fuente
1
No, no hay forma de que esto se pueda restringir en tiempo de compilación. IsFlag<T>determina si la enumeración se le ha [FlagsAttribute]aplicado y el CLR no tiene restricciones basadas en atributos. Sería en un mundo perfecto, o habría alguna otra forma de restringirlo, pero en este caso simplemente no funciona :(
Jon Skeet
(+1 para el principio general, sin embargo, me encantaría poder restringirlo)
Jon Skeet
9

Usaría NotSupportedException ya que eso es lo que está diciendo. Otros enumeraciones que los específicos son no compatibles . Por supuesto, esto se indicará más claramente en el mensaje de excepción.

Robban
fuente
2
NotSupportedException se usa para un propósito muy diferente en BCL. No encaja aquí. blogs.msdn.com/jaredpar/archive/2008/12/12/…
JaredPar
8

Yo iría con NotSupportedException. Si ArgumentExceptionbien se ve bien, realmente se espera cuando un argumento pasado a un método es inaceptable. Un argumento de tipo es una característica definitoria del método real al que desea llamar, no un "argumento" real. InvalidOperationExceptiondebe lanzarse cuando la operación que está realizando puede ser válida en algunos casos, pero para la situación particular, es inaceptable.

NotSupportedExceptionse lanza cuando una operación no es inherentemente compatible. Por ejemplo, al implementar una interfaz donde un miembro en particular no tiene sentido para una clase. Esto parece una situación similar.

Mehrdad Afshari
fuente
Mmm. Todavía no todo se siente bien, pero creo que va a ser lo más parecido a ella.
Jon Skeet
Jon: no se siente bien porque, naturalmente, esperamos que el compilador lo detecte.
Mehrdad Afshari
Sip. Este es un tipo extraño de restricción que me gustaría aplicar pero no puedo :)
Jon Skeet
6

Aparentemente, Microsoft usa ArgumentExceptionpara eso, como se demuestra en el ejemplo de Expression.Lambda <> , Enum.TryParse <> o Marshal.GetDelegateForFunctionPointer <> en la sección Excepciones. Tampoco pude encontrar ningún ejemplo que indique lo contrario (a pesar de buscar una fuente de referencia local para TDelegatey TEnum).

Por lo tanto, creo que es seguro asumir que al menos en el código de Microsoft es una práctica común usar ArgumentExceptionpara argumentos de tipo genérico no válidos además de los de variable básica. Dado que la descripción de la excepción en los documentos no discrimina entre ellos, tampoco es demasiado exagerado.

Ojalá se decida la cuestión de una vez por todas.

Alicia
fuente
Un solo ejemplo en el marco no es suficiente para mí, no - dado el número de lugares donde creo que MS ha hecho mala elección en otros casos :) No se derivaría TypeArgumentExceptionde ArgumentException, simplemente porque un argumento de tipo no es un habitual argumento.
Jon Skeet
1
Eso es ciertamente más convincente en términos de "es lo que la EM hace constantemente". No lo hace más convincente en términos de hacer coincidir la documentación ... y sé que hay muchas personas en el equipo de C # que se preocupan profundamente por la diferencia entre los argumentos regulares y los argumentos de tipo :) Pero gracias por los ejemplos - son muy útiles.
Jon Skeet
@Jon Skeet: Hizo una edición; ahora incluye 3 ejemplos de diferentes bibliotecas de MS, todas con ArgumentException documentada como la lanzada; así que si es una mala elección, al menos es una mala elección constante. ;) Supongo que Microsoft asume que los argumentos regulares y los argumentos de tipo son ambos argumentos; y personalmente, creo que tal suposición es bastante razonable. ^^ '
Alice
Ah, no importa, parece que ya te diste cuenta. Me alegro de haber podido ayudar. ^^
Alice
Creo que tendremos que estar de acuerdo en estar en desacuerdo sobre si es razonable tratarlos de la misma manera. Ciertamente, no son los mismos en lo que respecta a la reflexión, las reglas del lenguaje, etc., se manejan de manera muy diferente.
Jon Skeet
3

Iré con NotSupportedExpcetion.

Carl Bergquist
fuente
2

Lanzar una excepción personalizada siempre debe realizarse en cualquier caso en el que sea cuestionable. Una excepción personalizada siempre funcionará, independientemente de las necesidades de los usuarios de la API. El desarrollador podría detectar cualquier tipo de excepción si no le importa, pero si el desarrollador necesita un manejo especial, será SOL.

Eric Schneider
fuente
Además, el desarrollador debe documentar todas las excepciones incluidas en los comentarios XML.
Eric Schneider
1

¿Qué tal heredar de NotSupportedException? Si bien estoy de acuerdo con @Mehrdad en que tiene más sentido, escuché tu punto de que no parece encajar perfectamente. Por lo tanto, herede de NotSupportedException, y de esa manera las personas que codifican contra su API aún pueden detectar una NotSupportedException.

BFree
fuente
1

Siempre desconfío de escribir excepciones personalizadas, simplemente porque no siempre se documentan claramente y causan confusión si no se nombran correctamente.

En este caso, lanzaría una ArgumentException para la falla de verificación de banderas. En realidad, todo depende de las preferencias. Algunos estándares de codificación que he visto van tan lejos como para definir qué tipos de excepciones deben lanzarse en escenarios como este.

Si el usuario intentaba pasar algo que no era una enumeración, lanzaría una InvalidOperationException.

Editar:

Los demás plantean un punto interesante de que esto no es compatible. Mi única preocupación con una NotSupportedException es que generalmente esas son las excepciones que se lanzan cuando se ha introducido "materia oscura" en el sistema, o para decirlo de otra manera, "Este método debe ingresar al sistema en esta interfaz, pero ganamos no lo encienda hasta la versión 2.4 "

También he visto NotSupportedExceptions lanzarse como una excepción de licencia "está ejecutando la versión gratuita de este software, esta función no es compatible".

Edición 2:

Otro posible:

System.ComponentModel.InvalidEnumArgumentException  

La excepción lanzada cuando se utilizan argumentos no válidos que son enumeradores.

Pedro
fuente
Lo limitaré a ser una enumeración (después de un poco de jiggery póquer); solo me preocupan las banderas.
Jon Skeet
Creo que esos tipos de licencias deberían lanzar una instancia de una LicensingExceptionclase heredada InvalidOperationException.
Mehrdad Afshari
Estoy de acuerdo, Mehrdad. Desafortunadamente, las excepciones son una de esas áreas donde hay mucho gris en el marco. Pero estoy seguro de que esto es lo mismo para muchos idiomas. (no digo que volvería al error 13 en tiempo de ejecución de vb6, jeje)
Peter