¿Cuándo es apropiado lanzar una excepción desde dentro de un captador o definidor de propiedad? ¿Cuándo no es apropiado? ¿Por qué? Los enlaces a documentos externos sobre el tema serían útiles ... Google resultó sorprendentemente poco.
¿Cuándo es apropiado lanzar una excepción desde dentro de un captador o definidor de propiedad? ¿Cuándo no es apropiado? ¿Por qué? Los enlaces a documentos externos sobre el tema serían útiles ... Google resultó sorprendentemente poco.
Respuestas:
Microsoft tiene sus recomendaciones sobre cómo diseñar propiedades en http://msdn.microsoft.com/en-us/library/ms229006.aspx
Esencialmente, recomiendan que los compradores de propiedades sean accesos ligeros a los que siempre se pueda llamar con seguridad. Recomiendan rediseñar los getters para que sean métodos si es necesario lanzar excepciones. Para los establecedores, indican que las excepciones son una estrategia de manejo de errores apropiada y aceptable.
Para los indexadores, Microsoft indica que es aceptable tanto para los captadores como para los definidores lanzar excepciones. Y de hecho, muchos indexadores de la biblioteca .NET hacen esto. La excepción más común es ArgumentOutOfRangeException
.
Hay algunas razones bastante buenas por las que no desea lanzar excepciones en los captadores de propiedades:
obj.PropA.AnotherProp.YetAnother
- con este tipo de sintaxis resulta problemático decidir dónde inyectar las sentencias catch de excepción.Como nota al margen, uno debe tener en cuenta que el hecho de que una propiedad no esté diseñada para lanzar una excepción no significa que no lo hará; fácilmente podría estar llamando a un código que lo hace. Incluso el simple acto de asignar un nuevo objeto (como una cadena) podría resultar en excepciones. Siempre debe escribir su código a la defensiva y esperar excepciones de cualquier cosa que invoque.
No hay nada de malo en lanzar excepciones de los establecedores. Después de todo, ¿qué mejor manera de indicar que el valor no es válido para una propiedad determinada?
Para los captadores, generalmente está mal visto, y eso se puede explicar con bastante facilidad: un captador de propiedad, en general, informa el estado actual de un objeto; por lo tanto, el único caso en el que es razonable que un getter lance es cuando el estado no es válido. Pero generalmente también se considera una buena idea diseñar sus clases de manera que simplemente no sea posible obtener un objeto inválido inicialmente, o ponerlo en un estado inválido a través de medios normales (es decir, asegurar siempre la inicialización completa en los constructores, y intente hacer que los métodos sean seguros para excepciones con respecto a la validez de estado y las invariantes de clase). Siempre que cumpla con esa regla, los captadores de su propiedad nunca deben entrar en una situación en la que tengan que informar un estado no válido y, por lo tanto, nunca lanzar.
Hay una excepción que conozco, y en realidad es bastante importante: cualquier objeto que se implemente IDisposable
. Dispose
está específicamente diseñado como una forma de llevar el objeto a un estado no válido, e incluso hay una clase de excepción especial ObjectDisposedException
, para usarse en ese caso. Es perfectamente normal lanzar ObjectDisposedException
desde cualquier miembro de la clase, incluidos los captadores de propiedades (y excluirse a Dispose
sí mismo), después de que el objeto haya sido eliminado.
IDisposable
deben ser inutilizados después de un Dispose
. Si invocar a un miembro requeriría el uso de un recurso que no Dispose
está disponible (por ejemplo, el miembro leería datos de una secuencia que se ha cerrado), el miembro debería lanzar en ObjectDisposedException
lugar de filtrar ArgumentException
, por ejemplo , pero si uno tiene un formulario con propiedades que representan la valores en ciertos campos, parecería mucho más útil permitir que tales propiedades se lean después de la eliminación (produciendo los últimos valores escritos) que requerir ...
Dispose
se aplazarán hasta que se hayan leído todas esas propiedades. En algunos casos en los que un hilo puede usar lecturas de bloqueo en un objeto mientras otro lo cierra, y donde los datos pueden llegar en cualquier momento antes Dispose
, puede ser útil Dispose
cortar los datos entrantes, pero permitir que se lean los datos recibidos previamente. No se debe forzar una distinción artificial entre Close
y Dispose
en situaciones en las que de otro modo no debería existir.
Get...
método. Una excepción aquí es cuando tiene que implementar una interfaz existente que requiere que proporcione una propiedad.
Casi nunca es apropiado para un getter y, a veces, apropiado para un colocador.
El mejor recurso para este tipo de preguntas es "Framework Design Guidelines" de Cwalina y Abrams; está disponible como libro encuadernado y gran parte de él también está disponible en línea.
De la sección 5.2: Diseño de la propiedad
EVITE lanzar excepciones de los captadores de propiedades. Los captadores de propiedades deben ser operaciones simples y no deben tener condiciones previas. Si un captador puede lanzar una excepción, probablemente debería rediseñarse para que sea un método. Tenga en cuenta que esta regla no se aplica a los indexadores, donde esperamos excepciones como resultado de validar los argumentos.
Tenga en cuenta que esta pauta solo se aplica a los compradores de propiedades. Está bien lanzar una excepción en un establecedor de propiedades.
ObjectDisposedException
una vez que el objeto ha sido Dispose()
llamado y algo posteriormente solicita un valor de propiedad? Parece que la guía debería ser "evitar lanzar excepciones de los captadores de propiedades, a menos que el objeto haya sido eliminado, en cuyo caso debería considerar lanzar una ObjectDisposedExcpetion".
Un buen enfoque para las excepciones es usarlas para documentar el código para usted y otros desarrolladores de la siguiente manera:
Las excepciones deben ser para estados de programas excepcionales. Esto significa que está bien escribirlos donde quieras.
Una razón por la que puede querer ponerlos en getters es para documentar la API de una clase: si el software genera una excepción tan pronto como un programador intenta usarlo incorrectamente, ¡no lo usará incorrectamente! Por ejemplo, si tiene validación durante un proceso de lectura de datos, es posible que no tenga sentido poder continuar y acceder a los resultados del proceso si hubo errores fatales en los datos. En este caso, es posible que desee obtener el lanzamiento de salida si hubo errores para asegurarse de que otro programador verifique esta condición.
Son una forma de documentar los supuestos y los límites de un subsistema / método / lo que sea. En el caso general, ¡no deberían ser atrapados! Esto también se debe a que nunca se lanzan si el sistema funciona en conjunto de la manera esperada: si ocurre una excepción, muestra que no se cumplen las suposiciones de un fragmento de código, por ejemplo, no está interactuando con el mundo que lo rodea de la manera originalmente estaba destinado a hacerlo. Si detecta una excepción que se escribió para este propósito, probablemente signifique que el sistema ha entrado en un estado impredecible / inconsistente; esto, en última instancia, puede provocar una falla o corrupción de datos o similar, lo que probablemente sea mucho más difícil de detectar / depurar.
Los mensajes de excepción son una forma muy burda de informar errores: no se pueden recopilar en masa y solo contienen una cadena. Esto los hace inadecuados para informar problemas en los datos de entrada. En funcionamiento normal, el sistema en sí no debería entrar en un estado de error. Como resultado de esto, los mensajes en ellos deben diseñarse para programadores y no para usuarios; las cosas que están mal en los datos de entrada se pueden descubrir y transmitir a los usuarios en formatos más adecuados (personalizados).
La excepción (¡jaja!) A esta regla son cosas como IO, donde las excepciones no están bajo su control y no se pueden verificar con anticipación.
Todo esto está documentado en MSDN (como se vincula en otras respuestas) pero aquí hay una regla general ...
En el setter, si su propiedad debe validarse más allá del tipo. Por ejemplo, una propiedad llamada PhoneNumber probablemente debería tener validación de expresiones regulares y debería generar un error si el formato no es válido.
Para los captadores, posiblemente cuando el valor es nulo, pero lo más probable es que sea algo que querrá manejar en el código de llamada (según las pautas de diseño).
MSDN: tipos de excepción estándar de captura y lanzamiento
Esta es una pregunta muy compleja y la respuesta depende de cómo se utilice su objeto. Como regla general, los captadores y definidores de propiedades que son "vinculantes tardíos" no deben generar excepciones, mientras que las propiedades con "vinculación anticipada" exclusivamente deben generar excepciones cuando surja la necesidad. Por cierto, en mi opinión, la herramienta de análisis de código de Microsoft define el uso de propiedades de forma demasiado estricta.
"unión tardía" significa que las propiedades se encuentran por reflexión. Por ejemplo, el atributo Serializeable "se usa para serializar / deserializar un objeto a través de sus propiedades. Lanzar una excepción durante este tipo de situación rompe las cosas de una manera catastrófica y no es una buena manera de usar excepciones para hacer un código más robusto.
"enlace anticipado" significa que el uso de una propiedad está enlazado en el código por el compilador. Por ejemplo, cuando algún código que escribe hace referencia a un captador de propiedad. En este caso, está bien lanzar excepciones cuando tengan sentido.
Un objeto con atributos internos tiene un estado determinado por los valores de esos atributos. Las propiedades que expresan atributos que son conscientes y sensibles al estado interno del objeto no deben usarse para el enlace tardío. Por ejemplo, digamos que tiene un objeto que debe abrirse, accederse y luego cerrarse. En este caso, acceder a las propiedades sin llamar a open primero debería resultar en una excepción. Supongamos, en este caso, que no lanzamos una excepción y permitimos que el código acceda a un valor sin lanzar una excepción. El código parecerá feliz a pesar de que obtuvo un valor de un captador que no tiene sentido. Ahora hemos puesto el código que llamó al getter en una mala situación ya que debe saber comprobar el valor para ver si no tiene sentido. Esto significa que el código debe hacer suposiciones sobre el valor que obtuvo del captador de propiedad para poder validarlo. Así es como se escribe el código incorrecto.
Tenía este código donde no estaba seguro de qué excepción lanzar.
public Person
{
public string Name { get; set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
if (person.Name == null) {
throw new Exception("Name of person is null.");
// I was unsure of which exception to throw here.
}
Console.WriteLine("Name is: " + person.Name);
}
En primer lugar, evité que el modelo tuviera la propiedad nula forzándola como argumento en el constructor.
public Person
{
public Person(string name)
{
if (name == null) {
throw new ArgumentNullException(nameof(name));
}
Name = name;
}
public string Name { get; private set; }
public boolean HasPets { get; set; }
}
public void Foo(Person person)
{
Console.WriteLine("Name is: " + person.Name);
}