use - working with exceptions c#




Pegue várias exceções de uma só vez? (18)

Advertido e advertido: Ainda outro tipo, estilo funcional.

O que está no link não responde diretamente à sua pergunta, mas é trivial estendê-lo da seguinte forma:

static void Main() 
{ 
    Action body = () => { ...your code... };

    body.Catch<InvalidOperationException>() 
        .Catch<BadCodeException>() 
        .Catch<AnotherException>(ex => { ...handler... })(); 
}

(Basicamente, fornece outra Catchsobrecarga vazia que retorna a si mesma)

A maior questão é por quê . Eu não acho que o custo supera o ganho aqui :)

É desencorajado simplesmente pegar System.Exception . Em vez disso, apenas as exceções "conhecidas" devem ser capturadas.

Agora, isso às vezes leva a um código repetitivo desnecessário, por exemplo:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Eu me pergunto: existe uma maneira de capturar as duas exceções e só chamar a chamada de WebId = Guid.Empty uma vez?

O exemplo dado é bastante simples, pois é apenas um GUID . Mas imagine o código onde você modifica um objeto várias vezes, e se uma das manipulações falhar de uma maneira esperada, você deseja "redefinir" o object . No entanto, se houver uma exceção inesperada, ainda quero jogar isso mais alto.


@Micheal

Versão ligeiramente revisada do seu código:

catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) || 
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      throw;
   }
}

As comparações de strings são feias e lentas.


Com C # 7, a resposta de Michael Stum pode ser melhorada, mantendo a legibilidade de uma instrução switch:

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

Como outros apontaram, você pode ter uma declaração if dentro de seu bloco catch para determinar o que está acontecendo. C # 6 suporta Filtros de Exceção, então o seguinte irá funcionar:

try {  }
catch (Exception e) when (MyFilter(e))
{
    
}

O método MyFilter poderia ser algo como isto:

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

Alternativamente, isso pode ser feito em linha (o lado direito da instrução when precisa ser uma expressão booleana).

try {  }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    
}

Isso é diferente de usar uma instrução if de dentro do bloco catch , usando filtros de exceção que não irão desanuviar a pilha.

Você pode baixar o Visual Studio 2015 para verificar isso.

Se você quiser continuar usando o Visual Studio 2013, você pode instalar o seguinte pacote nuget:

Pacote de instalação Microsoft.Net.Compilers

No momento da escrita, isso incluirá suporte para C # 6.

Fazer referência a esse pacote fará com que o projeto seja criado usando a versão específica dos compiladores C # e Visual Basic contidos no pacote, em oposição a qualquer versão instalada do sistema.


Esta é uma variante da resposta de Matt (eu sinto que isso é um pouco mais limpo) ... use um método:

public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

Quaisquer outras exceções serão lançadas e o código WebId = Guid.Empty; não será atingido. Se você não quiser que outras exceções travem seu programa, basta adicionar isso DEPOIS das outras duas capturas:

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}

Filtros de exceção estão agora disponíveis em c # 6 +. Você pode fazer

try
{
       WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
     WebId = Guid.Empty;
}

No C # 6, a abordagem recomendada é usar Filtros de exceção, aqui está um exemplo:

 try
 {
      throw new OverflowException();
 }
 catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
 {
       // this will execute iff e is DividedByZeroEx or OverflowEx
       Console.WriteLine("E");
 }

Pegue System.Exception e ligue os tipos

catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }

    throw;
}

Se você não quiser usar uma instrução if nos escopos de catch , no C# 6.0 você pode usar a sintaxe de Exception Filters que já era suportada pelo CLR nas versões de visualizações, mas existia apenas no VB.NET / MSIL :

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Este código irá capturar a Exception somente quando é um InvalidDataException ou ArgumentNullException .

Na verdade, você pode colocar basicamente qualquer condição dentro dessa cláusula when :

static int a = 8;

...

catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
    Console.WriteLine("Catch");
}

Observe que, ao contrário de uma instrução if dentro do escopo da catch , os Exception Filters não podem lançar Exceptions e, quando o fazem, ou quando a condição não é true , a próxima condição de catch será avaliada:

static int a = 7;

static int b = 0;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Saída: captura geral.

Quando houver mais de um Exception Filter true o primeiro será aceito:

static int a = 8;

static int b = 4;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Saída: Catch.

E, como você pode ver no MSIL o código não é convertido para instruções if , mas para Filters e Exceptions não podem ser lançadas de dentro das áreas marcadas com o Filter 1 e Filter 2 mas o filtro que lança a Exception falhará. valor de comparação empurrado para a pilha antes que o comando endfilter determine o sucesso / falha do filtro ( Catch 1 XOR Catch 2 executará de acordo):

Além disso, especificamente Guid tem o método Guid.TryParse .


Se você puder atualizar seu aplicativo para C # 6, terá sorte. A nova versão C # implementou filtros de exceção. Então você pode escrever isto:

catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
    WebId = Guid.Empty;
}

Algumas pessoas acham que esse código é o mesmo que

catch (Exception ex) {                
    if (ex is FormatException || ex is OverflowException) {
        WebId = Guid.Empty;
    }
    throw;
}

Mas isso não. Na verdade, este é o único novo recurso no C # 6 que não é possível emular nas versões anteriores. Primeiro, um novo lançamento significa mais sobrecarga do que pular a captura. Em segundo lugar, não é semanticamente equivalente. O novo recurso preserva a pilha intacta quando você está depurando seu código. Sem esse recurso, o despejo de memória é menos útil ou até inútil.

Veja uma roslyn.codeplex.com/discussions/541301 . E um exemplo mostrando a diferença .


EDIT: Eu concordo com os outros que estão dizendo que, a partir do C # 6.0, os filtros de exceção são agora uma maneira perfeita de ir: catch (Exception ex) when (ex is ... || ex is ... )

Só que eu ainda odeio o layout de uma linha longa e coloco o código como se segue. Acho que isso é tão funcional quanto estético, pois acredito que melhora a compreensão. Alguns podem discordar:

catch (Exception ex) when (
    ex is ...
    || ex is ...
    || ex is ...
)

ORIGINAL:

Eu sei que estou um pouco atrasado para a festa aqui, mas a fumaça sagrada ...

Cortando direto ao ponto, esse tipo de duplica uma resposta anterior, mas se você realmente quiser executar uma ação comum para vários tipos de exceção e manter tudo limpo e organizado dentro do escopo de um método, por que não usar apenas um lambda? / encerramento / inline função para fazer algo como o seguinte? Quero dizer, as chances são muito boas que você acabará percebendo que você só quer fazer desse fechamento um método separado que você pode utilizar em todo o lugar. Mas então será super fácil fazer isso sem realmente alterar o resto do código estruturalmente. Certo?

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

    try
    {
        // try some stuff
    }
    catch ( FormatException  ex ) { errorHandler ( ex ); }
    catch ( OverflowException ex ) { errorHandler ( ex ); }
    catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}

Eu não posso ajudar, mas pergunto ( aviso: um pouco de ironia / sarcasmo à frente) por que diabos ir a todo esse esforço para basicamente apenas substituir o seguinte:

try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

... com alguma variação louca desse próximo código, quero dizer exemplo, apenas para fingir que você está salvando alguns toques no teclado.

// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

Porque certamente não é automaticamente mais legível.

Concedido, deixei as três instâncias idênticas de /* write to a log, whatever... */ return; fora do primeiro exemplo.

Mas esse é o meu ponto. Vocês já ouviram falar de funções / métodos, certo? A sério. Escreva uma função comum ErrorHandler e, assim, chame-a de cada bloco catch.

Se você me perguntar, o segundo exemplo (com as palavras is chave if e is ) é significativamente menos legível e, ao mesmo tempo, significativamente mais propenso a erros durante a fase de manutenção de seu projeto.

A fase de manutenção, para quem pode ser relativamente novo na programação, vai incluir 98,7% ou mais da vida útil total do seu projeto, e o pobre idiota que faz a manutenção certamente será alguém diferente de você. E há uma boa chance de que eles gastem 50% do seu tempo no trabalho amaldiçoando seu nome.

E, claro, o FxCop late com você e então você também tem que adicionar um atributo ao seu código que tenha precisamente o zip do programa em execução, e está lá apenas para dizer ao FxCop para ignorar um problema que em 99.9% dos casos é totalmente correto na sinalização. E, desculpe, posso estar enganado, mas esse atributo "ignore" não é compilado no seu aplicativo?

Colocaria o teste if inteiro em uma linha para torná-lo mais legível? Acho que não. Quero dizer, eu tive outro programador argumentando veementemente, há muito tempo atrás, que colocar mais código em uma linha faria com que ele "corresse mais rápido". Mas é claro que ele era louco por nozes. Tentando explicar a ele (com uma cara séria - o que era desafiador) como o interpretador ou compilador separaria essa longa linha em instruções discretas de uma instrução por linha - essencialmente idênticas ao resultado se ele tivesse ido em frente e acabou de tornar o código legível em vez de tentar descobrir o compilador - não teve nenhum efeito nele. Mas eu divago.

Quanto menos legível isso acontece quando você adiciona mais três tipos de exceção, um mês ou dois a partir de agora? (Resposta: fica muito menos legível).

Um dos principais pontos, na verdade, é que a maior parte do ponto de formatação do código-fonte textual que estamos vendo todos os dias é tornar realmente óbvio para outros seres humanos o que realmente está acontecendo quando o código é executado. Porque o compilador transforma o código-fonte em algo totalmente diferente e não poderia se importar menos com o seu estilo de formatação de código. Então tudo-em-um-linha totalmente suga também.

Apenas dizendo...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

Basta ligar para tentar e pegar duas vezes.

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
try
{
    WebId = new Guid(queryString["web"]);
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

É apenas isso simples!


Esse é um problema clássico que todo desenvolvedor C # enfrenta eventualmente.

Deixe-me dividir sua pergunta em duas perguntas. O primeiro,

Posso pegar várias exceções de uma só vez?

Em suma, não.

O que leva à próxima pergunta

Como evito escrever código duplicado, uma vez que não consigo capturar vários tipos de exceção no mesmo bloco catch ()?

Dada a sua amostra específica, onde o valor de fall-back é barato para construir, eu gosto de seguir estes passos:

  1. Inicialize o WebId para o valor de fall-back.
  2. Construa um novo Guid em uma variável temporária.
  3. Configure o WebId para a variável temporária totalmente construída. Faça desta a declaração final do bloco try {}.

Então o código parece:

try
{
    WebId = Guid.Empty;
    Guid newGuid = new Guid(queryString["web"]);
    // More initialization code goes here like 
    // newGuid.x = y;
    WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}

Se qualquer exceção for lançada, o WebId nunca será definido como o valor parcialmente construído e permanecerá como Guid.Empty.

Se construir o valor de fall-back for caro e redefinir um valor for muito mais barato, moverei o código de redefinição para sua própria função:

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}

No c # 6.0, os Filtros de exceção são aprimoramentos para o tratamento de exceções

try
{
    DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
    switch (e.GetHttpCode())
    {
        case 400:
            WriteLine("Bad Request");
        case 500:
            WriteLine("Internal Server Error");
        default:
            WriteLine("Generic Error");
    }
}

Atualização 2015-12-15: Veja https://.com/a/22864936/1718702 para o C # 6. É mais limpo e agora padrão na linguagem.

Voltado para pessoas que querem uma solução mais elegante para capturar uma vez e filtrar exceções, eu uso um método de extensão como demonstrado abaixo.

Eu já tinha essa extensão na minha biblioteca, originalmente escrita para outros propósitos, mas funcionava perfeitamente para typechecar exceções. Além disso, parece mais limpo do que um monte de ||declarações. Além disso, ao contrário da resposta aceita, eu prefiro o tratamento explícito de exceções, de modo que ex is ...tive um comportamento indesejável, já que as classes derivadas são atribuíveis a esses tipos de pai).

Uso

if (ex.GetType().IsAnyOf(
    typeof(FormatException),
    typeof(ArgumentException)))
{
    // Handle
}
else
    throw;

Extensão IsAnyOf.cs (Veja Exemplo de Tratamento de Erros Completo para Dependências)

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }
    }
}

Exemplo de tratamento de erro completo (Copiar e colar para o novo aplicativo do console)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;

namespace IsAnyOfExceptionHandlerSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // High Level Error Handler (Log and Crash App)
            try
            {
                Foo();
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
                Console.ReadKey();
            }
        }

        static void Foo()
        {
            // Init
            List<Action<string>> TestActions = new List<Action<string>>()
            {
                (key) => { throw new FormatException(); },
                (key) => { throw new ArgumentException(); },
                (key) => { throw new KeyNotFoundException();},
                (key) => { throw new OutOfMemoryException(); },
            };

            // Run
            foreach (var FooAction in TestActions)
            {
                // Mid-Level Error Handler (Appends Data for Log)
                try
                {
                    // Init
                    var SomeKeyPassedToFoo = "FooParam";

                    // Low-Level Handler (Handle/Log and Keep going)
                    try
                    {
                        FooAction(SomeKeyPassedToFoo);
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType().IsAnyOf(
                            typeof(FormatException),
                            typeof(ArgumentException)))
                        {
                            // Handle
                            Console.WriteLine("ex was {0}", ex.GetType().Name);
                            Console.ReadKey();
                        }
                        else
                        {
                            // Add some Debug info
                            ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
                            throw;
                        }
                    }
                }
                catch (KeyNotFoundException ex)
                {
                    // Handle differently
                    Console.WriteLine(ex.Message);

                    int Count = 0;
                    if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
                        foreach (var Key in ex.Data.Keys)
                            Console.WriteLine(
                                "[{0}][\"{1}\" = {2}]",
                                Count, Key, ex.Data[Key]);

                    Console.ReadKey();
                }
            }
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }

        /// <summary>
        /// Validates if any passed in parameter is equal to null.
        /// </summary>
        /// <param name="p_parameters">Parameters to test for Null.</param>
        /// <returns>True if one or more parameters are null.</returns>
        public static bool IsAnyNull(params object[] p_parameters)
        {
            p_parameters
                .CannotBeNullOrEmpty("p_parameters");

            foreach (var item in p_parameters)
                if (item == null)
                    return true;

            return false;
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CannotBeNull(this object p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(
                        string.Format("Parameter \"{0}\" cannot be null.",
                        p_name), default(Exception));
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
        }
    }
}

Testes de Unidade NUnit de Duas Amostras

O comportamento de correspondência para Exceptiontipos é exato (ou seja, uma criança NÃO é uma correspondência para qualquer um dos seus tipos de pai).

using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

namespace UnitTests.Common.Fluent_Validations
{
    [TestFixture]
    public class IsAnyOf_Tests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
        {
            Action TestMethod = () => { throw new ArgumentNullException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
                    typeof(FormatException),
                    typeof(KeyNotFoundException)))
                {
                    // Handle expected Exceptions
                    return;
                }

                //else throw original
                throw;
            }
        }

        [Test, ExpectedException(typeof(OutOfMemoryException))]
        public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
        {
            Action TestMethod = () => { throw new OutOfMemoryException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(OutOfMemoryException),
                    typeof(Exception)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}

Então você está repetindo muitos códigos dentro de cada exceção? Parece que extrair um método seria uma ideia divina, não é?

Então, seu código resume isso:

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

Eu me pergunto por que ninguém percebeu essa duplicação de código.

Do C # 6 você também tem os exception-filters como já mencionado por outros. Então você pode modificar o código acima para isso:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}

Queria ter adicionado minha resposta curta a esse longo tópico. Algo que não foi mencionado é a ordem de precedência das declarações catch, mais especificamente você precisa estar ciente do escopo de cada tipo de exceção que você está tentando capturar.

Por exemplo, se você usar uma exceção "pega-tudo" como Exceção, ele irá preceder todas as outras instruções catch e você obviamente obterá erros do compilador, no entanto, se você inverter a ordem, você pode encadear suas declarações catch (bit de um antipadrão eu acho ) você pode colocar o tipo Exception catch-all na parte inferior e isso irá capturar todas as exceções que não atendem mais alto em seu bloco try..catch:

            try
            {
                // do some work here
            }
            catch (WebException ex)
            {
                // catch a web excpetion
            }
            catch (ArgumentException ex)
            {
                // do some stuff
            }
            catch (Exception ex)
            {
                // you should really surface your errors but this is for example only
                throw new Exception("An error occurred: " + ex.Message);
            }

Eu recomendo altamente pessoal rever este documento MSDN:

Hierarquia de exceções


catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}




exception-handling