Como capturar todas as variantes de uma exceção genérica em C #

22

Gostaria de pegar todas as variantes de uma classe de exceção genérica e queria saber se existe uma maneira de fazer isso sem vários blocos de captura. Por exemplo, digamos que eu tenho uma classe de exceção:

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

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

e duas classes derivadas:

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

    }
}

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

    }
}

existe uma maneira de capturar ambos MyDerivedStringExceptione MyDerivedIntExceptionem um bloco de captura.

Eu tentei isso:

try
{
   ...
}

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

mas não é muito limpo e significa que não tenho acesso MyProperty.

Estou interessado em uma solução geral para o problema, mas no meu caso, a exceção genérica é definida em uma biblioteca de terceiros que, conforme indicado abaixo, adiciona algumas restrições adicionais ao problema.

SBFrancies
fonte

Respostas:

15

Faça MyException<T>implementar uma interface e verifique se há uma exceção pelo tipo de interface.

Interface:

public interface IMyException
{
    string MyProperty { get; }
}

Classe genérica que implementa a interface:

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

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

Classes 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)
{
    // ...
}

O mesmo pode ser feito criando uma classe base que herda Exceptione que MyException<T>deriva dessa classe base.

Eliahu Aaron
fonte
3
Não tenho certeza de que vale a pena uma interface aqui - apenas uma classe base não genérica entre Exceptione MyException<T>seria adequada.
Jon Skeet
Além disso, o OP ainda não tem acesso à propriedade genérica (sem usar reflexão), o que eu acho que é o problema real.
DavidG 27/11/19
10

O teste se você Typeé derivado de um genérico é:

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

Mas isso é verdade apenas para os tipos derivados em si, como MyException<int>ou MyException<string>.

Se você tem mais derivativos, como MyDerivedStringExceptionvocê teve que testar:

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

Portanto, isso funciona para qualquer genérico existente, mas você precisa saber o nível de herança para este teste ou percorrer todos os tipos de base.

Então você pode fazer isso:

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

Essa implementação encaixa e desmarca se T: Value for Valuetype ... mas com relação à utilização, você pode controlar as implicações de desempenho com o número de vezes que tenta encaixar / desmarcar.

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
fonte
Esta é uma boa resposta e já votei - infelizmente não resolve meu problema específico, pois a exceção genérica está em uma biblioteca de terceiros, portanto não posso editá-la.
SBFrancies
2

Infelizmente, você não pode pegar com o

catch(MyException ex) classe, pois espera um tipo.

Sua melhor aposta seria criar uma classe ou interface base, capturá-la e obter o tipo a partir daí.

Já existe uma resposta aqui, sobre como obter o tipo e fazer isso.

Athanasios Kataras
fonte
1

essa implementação abstrai o delegado de manipulação para o tipo de exceção, longe do contexto do T / C ... Isso pode ser um pouco aventureiro, mas a parte interessante é que você pode injetar diferentes manipuladores, dependendo do escopo de reutilização e do contexto de T / C. utilização.

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
fonte