Cómo capturar todas las variantes de una excepción genérica en C #

22

Me gustaría capturar todas las variantes de una clase de excepción genérica y me preguntaba si hay una manera de hacerlo sin múltiples bloques de captura. Por ejemplo, digamos que tengo una clase de excepción:

public class MyException<T> : Exception
{
    public string MyProperty { get; }

    public MyException(T prop) : base(prop.ToString())
    {
        MyProperty = prop?.ToString();
    }
}

y dos clases derivadas:

public class MyDerivedStringException : MyException<string>
{
    public MyDerivedStringException(string prop) : base(prop)
    {

    }
}

public class MyDerivedIntException : MyException<int>
{
    public MyDerivedIntException(int prop) : base(prop)
    {

    }
}

¿Hay alguna manera de atrapar a ambos MyDerivedStringExceptiony MyDerivedIntExceptionen un bloque de captura?

He intentado esto:

try
{
   ...
}

catch(Exception e) when (e is MyDerivedStringException || e is MyDerivedIntException)
{
}

pero no está muy limpio y significa que no tengo acceso MyProperty.

Estoy interesado en una solución general al problema, pero en mi caso la excepción genérica se define en una biblioteca de terceros que, como se señala a continuación, agrega algunas restricciones adicionales al problema.

SBFrancies
fuente

Respuestas:

15

Hacer MyException<T>implementar una interfaz y comprobar si hay una excepción por el tipo de interfaz.

Interfaz:

public interface IMyException
{
    string MyProperty { get; }
}

Clase genérica que implementa la interfaz:

public class MyException<T> : Exception, IMyException
{
    public string MyProperty { get; }

    public MyException(T prop)
    {
        MyProperty = prop?.ToString();
    }
}

Clases derivadas:

public class MyDerivedStringException : MyException<string>
{
    public MyDerivedStringException(string prop) : base(prop)
    {

    }
}

public class MyDerivedIntException : MyException<int>
{
    public MyDerivedIntException(int prop) : base(prop)
    {

    }
}

Uso:

try
{
    // ...
}
catch (Exception e) when (e is IMyException)
{
    // ...
}

Lo mismo se puede hacer mediante la creación de una clase base que herede Exceptiony que se MyException<T>derive de esa clase base.

Eliahu Aaron
fuente
3
No estoy seguro de que valga la pena una interfaz aquí, solo una clase base no genérica entre Exceptiony MyException<T>estaría bien.
Jon Skeet
Además, OP todavía no tiene acceso a la propiedad genérica (sin usar la reflexión), lo que supongo que es el verdadero problema.
DavidG
10

La prueba si su Typederiva de un genérico es:

Type = typeof(something);
t.GetGenericTypeDefinition()==typeof(MyException<>);

Pero esto es cierto solo para los tipos derivados en sí, like MyException<int>o MyException<string>.

Si tiene más derivados como los MyDerivedStringExceptionque tuvo que probar:

ex.GetType.BaseType.GetGenericTypeDefinition()==typeof(MyException<>);

Entonces, esto funciona para cualquier genérico existente, pero necesita conocer el nivel de herencia para esta prueba, o recorrer todos los tipos base.

Entonces podrías hacer esto:

catch(Exception ex) when (ex.GetType.BaseType.GetGenericTypeDefinition()==typeof(MyException<>))
Holger
fuente
3

Esta implementación encajona y desempaqueta si T: Value es Valuetype ... pero con respecto a la utilización puede controlar las implicaciones de rendimiento con la cantidad de veces que intenta encajonar / desempaquetar.

public class MyException<T> : MyException
{
    public T Prop => (T)base.Prop;

    public MyException(T prop) : base(prop)
    {

    }
}

public class MyException : Exception
{
    protected object Prop { get; }

    public MyException(object prop)
    {
         Prop = prop;
    }
}

Lógica

try {
     ...
} catch(MyException e) {
    ...
}
Trae Moore
fuente
Esta es una buena respuesta y he votado: desafortunadamente no resuelve mi problema específico, ya que la excepción genérica está en una biblioteca de terceros, por lo que no puedo editarla.
SBFrancies
2

Desafortunadamente, no puedes atrapar con el

catch(MyException ex) clase, como se espera un tipo.

Su mejor opción sería crear una clase base o interfaz, capturarla y obtener el tipo desde allí.

Ya existe una respuesta sobre lo que aquí sobre la manera de obtener el tipo y hacer esto.

Athanasios Kataras
fuente
1

esta implementación abstrae el delegado de manejo para el tipo de excepción fuera del contexto del T / C ... Esto puede ser un poco demasiado aventurero, pero lo bueno de esto es que puede inyectar diferentes manejadores dependiendo del alcance de la reutilización y el contexto de utilización.

public interface IExceptionHandler
{
    object Handle(Exception ex);

    void RegisterExceptionTypeHandler<T>(Func<T,object> handlerDelegate) where T : Exception;
}

lógica de registro

handler.RegisterExceptionTypeHandler<MyException<int>>(ex => ...);
handler.RegisterExceptionTypeHandler<MyException<string>>(ex => ...);

try {
    ...
}catch(Exception ex)
{
    ...any validation
    return exceptionHandler.Handle(ex)
}
Trae Moore
fuente