Comment intercepter toutes les variantes d'une exception générique en C #

22

Je voudrais attraper toutes les variantes d'une classe d'exception générique et je me demandais s'il y avait un moyen de le faire sans plusieurs blocs catch. Par exemple, disons que j'ai une classe d'exception:

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

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

et deux classes dérivées:

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

    }
}

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

    }
}

existe-t-il un moyen d'attraper les deux MyDerivedStringExceptionet MyDerivedIntExceptiondans un bloc de capture.

J'ai essayé ceci:

try
{
   ...
}

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

mais ce n'est pas très propre et signifie que je n'y ai pas accès MyProperty.

Je suis intéressé par une solution générale au problème, mais dans mon cas, l'exception générique est définie dans une bibliothèque tierce qui, comme indiqué ci-dessous, ajoute des contraintes supplémentaires au problème.

SBFrancies
la source

Réponses:

15

Faites MyException<T>implémenter une interface et recherchez une exception par le type d'interface.

Interface:

public interface IMyException
{
    string MyProperty { get; }
}

Classe générique implémentant l'interface:

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

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

Classes dérivées:

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

    }
}

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

    }
}

Usage:

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

La même chose peut être faite en créant une classe de base qui hérite de Exceptionet en faisant MyException<T>dériver de cette classe de base.

Eliahu Aaron
la source
3
Je ne suis pas sûr que cela vaille la peine d'une interface ici - juste une classe de base non générique entre Exceptionet ce MyException<T>serait bien.
Jon Skeet
De plus, OP n'a toujours pas accès à la propriété générique (sans utiliser la réflexion), ce qui, je suppose, est le vrai problème.
DavidG
10

Le test si votre Typeest dérivé d'un générique est:

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

Mais cela n'est vrai que pour les types dérivés eux-mêmes, comme MyException<int>ou MyException<string>.

Si vous avez d'autres dérivés comme MyDerivedStringExceptionvous avez dû tester:

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

Cela fonctionne donc pour tout générique existant, mais vous devez connaître le niveau d'héritage pour ce test, ou parcourir tous les types de base.

Vous pouvez donc faire ceci:

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

Cette implémentation met en boîte et décompresse si T: Value est Valuetype ... mais en ce qui concerne l'utilisation, vous pouvez contrôler les implications en termes de performances avec le nombre de fois que vous tentez de décompresser / décompresser.

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

Logique

try {
     ...
} catch(MyException e) {
    ...
}
Trae Moore
la source
C'est une bonne réponse et j'ai voté positivement - malheureusement, cela ne résout pas mon problème spécifique car l'exception générique se trouve dans une bibliothèque tierce, donc je ne peux pas la modifier.
SBFrancies
2

Malheureusement, vous ne pouvez pas attraper avec le

catch(MyException ex) classe, car il attend un type.

Votre meilleur pari serait de créer une classe ou une interface de base, de l'attraper et d'obtenir le type à partir de là.

Il y a déjà une réponse, alors voici comment obtenir le type et le faire.

Athanasios Kataras
la source
1

cette implémentation résume le délégué de gestion pour le type d'exception loin du contexte du T / C ... Cela peut être un peu trop aventureux, mais la partie intéressante à ce sujet est que vous pouvez injecter différents gestionnaires en fonction de la portée de la réutilisation et du contexte de utilisation.

public interface IExceptionHandler
{
    object Handle(Exception ex);

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

logique d'enregistrement

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

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