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

2140

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

Agora, isso às vezes leva ao 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 ligar apenas WebId = Guid.Emptyuma vez?

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

Michael Stum
fonte
5
Se você estiver usando .net 4 e acima i preferem usar aggregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
Bepenfriends
2
Bepenfriends- Como o System.Guid não lança AggregateException , seria ótimo se você (ou alguém) pudesse postar uma resposta mostrando como você o envolveria em um AggregateException etc.
weir
1
Sobre a utilização AggregateException: Jogando um AggregateException no meu próprio código
DavidRR
11
"É desencorajado simplesmente pegar o System.Exception." - e se o método pode lançar 32 tipos de exceções, o que se faz? escrever captura para cada um deles separadamente?
Giorgim # 23/15
5
Se um método está lançando 32 tipos diferentes de exceções, está mal escrito. Ou ele não está capturando exceções que suas próprias chamadas estão fazendo, está fazendo MUITO demais em um método, ou a maioria / todas essas 32 devem ser uma única exceção com um código de razão.
Flynn1179

Respostas:

2100

Pegar System.Exceptione ligar os tipos

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

    throw;
}
Joseph Daigle
fonte
69
Infelizmente, o FxCop (isto é, análise de código do Visual Studio) não gosta quando você pega o Exception.
Andrew Garrison
15
Concordo em não pegar a exceção, mas, nesse caso, a captura é um filtro. Você pode ter uma camada superior que lide com outros tipos de exceção. Eu diria que isso está correto, mesmo que inclua uma captura (exceção x). Ele não modifica o fluxo do programa, apenas lida com certas exceções e permite que o restante do aplicativo lide com outros tipos de exceção.
lkg
28
A versão mais recente do FxCop não gera uma exceção quando o código acima é usado.
Peter Peter
28
Não tenho certeza do que estava errado com o código do OP em primeiro lugar. A resposta nº 1 aceita é quase o dobro de linhas e muito menos legível.
João Bragança
22
@ JoãoBragança: Embora esta resposta neste exemplo use mais linhas, tente imaginar se você está lidando com E / S de arquivo, por exemplo, e tudo o que você quer fazer é capturar essas exceções e enviar mensagens de log, mas apenas as que você espera que sejam provenientes do seu métodos de E / S de arquivo. Então, você geralmente precisa lidar com um número maior (cerca de 5 ou mais) tipos diferentes de exceções. Nessa situação, essa abordagem pode economizar algumas linhas.
Xilconic
595

Edição: Eu concordo com outras pessoas que estão dizendo que, a partir do C # 6.0, filtros de exceção agora são um caminho perfeitamente correto:catch (Exception ex) when (ex is ... || ex is ... )

Só que eu ainda odeio o layout de uma linha longa e pessoalmente colocaria o código da seguinte maneira. Eu 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 fumaça sagrada ...

Indo direto ao assunto, esse tipo de duplicata uma resposta anterior, mas se você realmente deseja executar uma ação comum para vários tipos de exceção e manter a coisa toda limpa e arrumada no escopo do método, por que não usar apenas um lambda / encerramento / função embutida para fazer algo como o seguinte? Quero dizer, as chances são muito boas de que você acabe percebendo que deseja apenas tornar esse fechamento um método separado que possa ser utilizado em todo o lugar. Mas será super fácil fazer isso sem alterar o restante do código estruturalmente. Direita?

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

Não posso deixar de me perguntar ( aviso: um pouco de ironia / sarcasmo pela frente) por que diabos vamos a todo esse esforço para basicamente substituir apenas o seguinte:

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

... com uma variação louca desse próximo cheiro de código, quero dizer exemplo, apenas para fingir que você está salvando algumas teclas.

// 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.

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

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

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

A fase de manutenção, para qualquer um que seja relativamente novo em programação, abrangerá 98,7% ou mais da vida útil geral do seu projeto, e o pobre coitado que faz a manutenção certamente será alguém que não seja você. E há uma chance muito boa de que eles gastem 50% do seu tempo no trabalho amaldiçoando seu nome.

E, é claro, o FxCop late para você e, portanto, você também deve adicionar um atributo ao seu código que seja exatamente o que está relacionado ao programa em execução, e existe apenas para solicitar ao FxCop que ignore um problema que, em 99,9% dos casos, é totalmente correto na sinalização. E, desculpe, posso estar enganado, mas esse atributo "ignorar" acaba realmente compilado no seu aplicativo?

Colocar o ifteste inteiro em uma linha o tornaria mais legível? Acho que não. Quero dizer, eu tive outro programador argumentar veementemente, há muito tempo, que colocar mais código em uma linha o faria "correr mais rápido". Mas é claro que ele estava nojento. Tentando explicar a ele (com uma cara séria - o que foi desafiador) como o intérprete ou compilador dividiria essa longa linha em instruções discretas de uma instrução por linha - essencialmente idêntico ao resultado se ele tivesse ido em frente e apenas tornou o código legível em vez de tentar enganar o compilador - não teve nenhum efeito nele. Mas eu discordo.

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

Um dos principais pontos, na verdade, é que a maior parte do ponto de formatar o código-fonte textual que todos nós estamos vendo todos os dias é torná-lo muito, muito óbvio para outros seres humanos o que realmente está acontecendo quando o código é executado. Como o compilador transforma o código-fonte em algo totalmente diferente e não se importava com o seu estilo de formatação de código. Então, tudo em uma linha também é uma droga 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;
}
Craig
fonte
36
Quando me deparei com essa pergunta, encontrei toda a resposta aceita. Legal, eu posso pegar todos Exceptionos se checar o tipo. Eu pensei que tinha limpado o código, mas algo me fez voltar à pergunta e, na verdade, li as outras respostas para a pergunta. Eu mastiguei por um tempo, mas tenho que concordar com você. É mais legível e fácil de usar usar uma função para secar seu código do que pegar tudo, verificar o tipo de comparação com uma lista, quebrar o código e jogar. Agradecemos por chegar atrasado e por fornecer uma opção alternativa e sã (IMO). +1.
erroric
8
Usar uma função de tratamento de erros não funcionaria se você quisesse incluir a throw;. Você teria que repetir essa linha de código em cada bloco catch (obviamente não é o fim do mundo, mas vale a pena mencionar, pois é um código que precisa ser repetido).
kad81
5
@ kad81, isso é verdade, mas você ainda teria o benefício de escrever o código de log e de limpeza em um local e alterá-lo em um local, se necessário, sem a semântica pateta de pegar o tipo de exceção base e ramificar com base em o tipo de exceção. E essa throw();declaração extra em cada bloco de captura é um pequeno preço a pagar, IMO, e ainda permite que você faça uma limpeza adicional específica do tipo de exceção, se necessário.
Craig
2
Oi @ Reitffunk, basta usar em Func<Exception, MyEnumType>vez de Action<Exception>. Isso é Func<T, Result>, Resultsendo o tipo de retorno.
30715 Craig
3
Estou de pleno acordo aqui. Eu também li a primeira resposta e o pensamento parece lógico. Movido para um genérico 1 para todos os manipuladores de exceção. Alguma coisa dentro de mim me fez vomitar internamente ... então reverti o código. Então me deparei com essa beleza! Isso precisa ser a resposta aceita
Conor Gallagher
372

Como outros já apontaram, você pode ter uma ifdeclaração dentro do seu bloco de captura para determinar o que está acontecendo. O C # 6 suporta filtros de exceção, portanto, o seguinte funcionará:

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

O MyFiltermétodo pode ser algo parecido com isto:

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

Como alternativa, tudo isso pode ser feito em linha (o lado direito da instrução when só precisa ser uma expressão booleana).

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

Isso é diferente de usar uma ifinstrução de dentro do catchbloco, o uso de filtros de exceção não desanuviará a pilha.

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

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

Pacote de instalação Microsoft.Net.Compilers

No momento da redação deste artigo, isso incluirá suporte ao C # 6.

A referência a este 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.

Joe
fonte
3
Pacientemente aguardando o lançamento oficial de 6 ... Eu gostaria de ver isso chegar ao controle quando isso acontecer.
21415 RubberDuck
@RubberDuck Estou morrendo de vontade de trabalhar com o operador de propagação nula do C # 6. Tentando convencer o restante da minha equipe de que vale a pena o risco de uma linguagem / compilador instável. Muitas pequenas melhorias com enorme impacto. Quanto a ser marcado como resposta, não é importante, desde que as pessoas percebam que isso será / é possível, estou feliz.
21415 Joe Joe
Direita?! Vou dar uma boa olhada na minha base de código no futuro próximo. =) Sei que a verificação não é importante, mas, dada a resposta aceita em breve, está desatualizada, espero que o OP volte para verificar isso para dar a visibilidade adequada.
21415 RubberDuck
É parcialmente por isso que ainda não o premiei @ Joe. Eu quero que isso seja visível. Você pode querer adicionar um exemplo de filtro embutido para maior clareza.
RubberDuck
188

Infelizmente, não em C #, pois você precisaria de um filtro de exceção para fazer isso e o C # não expõe esse recurso do MSIL. O VB.NET possui esse recurso, por exemplo,

Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException

O que você pode fazer é usar uma função anônima para encapsular seu código de erro e chamá-lo nesses blocos de captura específicos:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}
Greg Beech
fonte
26
Ideia interessante e outro exemplo de que o VB.net às vezes tem algumas vantagens interessantes sobre o C # #
Michael Stum
47
@MichaelStum com esse tipo de sintaxe, eu dificilmente chamaria de interessante ... shudder #
MarioDS
17
Os filtros de exceção estão chegando no c # 6! Observe a diferença de usar filtros em favor do relançamento roslyn.codeplex.com/discussions/541301
Arne Deruwe
@ArneDeruwe Obrigado por esse link! Eu só aprendi uma razão mais importante para não re-lance: throw e;Stacktrace destrói e callstack, throw;destrói "apenas" callstack (renderização crash-dumps inútil!) Uma muito boa razão para usar nem se ele pode ser evitado!
AnorZaken
1
A partir do C # 6, filtros de exceção estão disponíveis! Finalmente.
Danny
134

Por uma questão de integridade, desde o .NET 4.0, o código pode ser reescrito como:

Guid.TryParse(queryString["web"], out WebId);

O TryParse nunca lança exceções e retorna false se o formato estiver errado, configurando WebId como Guid.Empty.


Desde o C # 7, você pode evitar a introdução de uma variável em uma linha separada:

Guid.TryParse(queryString["web"], out Guid webId);

Você também pode criar métodos para analisar as tuplas retornadas, que ainda não estão disponíveis no .NET Framework a partir da versão 4.6:

(bool success, Guid result) TryParseGuid(string input) =>
    (Guid.TryParse(input, out Guid result), result);

E use-os assim:

WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;

A próxima atualização inútil para essa resposta inútil ocorre quando a desconstrução de parâmetros externos é implementada em C # 12. :)

Athari
fonte
19
Precisamente - conciso e você ignora totalmente a penalidade de desempenho ao lidar com a exceção, a má forma de usar intencionalmente exceções para controlar o fluxo do programa e o foco suave de espalhar sua lógica de conversão, um pouco aqui e um pouco ali .
Craig
9
Eu sei o que você quis dizer, mas é claro que Guid.TryParsenunca volta Guid.Empty. Se a sequência estiver em um formato incorreto, ela definirá o resultparâmetro de saída como Guid.Empty, mas retornará false . Estou mencionando isso porque vi código que faz coisas no estilo de Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }, que geralmente está errado se spuder ser a representação de string de Guid.Empty.
14
uau, você respondeu à pergunta, exceto que ela não está no espírito da pergunta. O problema maior é outra coisa :(
Nawfal
6
O padrão apropriado para usar o TryParse, é claro, é mais parecido if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }, o que não deixa ambiguidade como o exemplo quebrado, em que o valor de entrada pode realmente ser a representação de string de um Guid.
Craig
2
Essa resposta pode de fato estar correta em relação ao Guid.Parse, mas perdeu todo o ponto da pergunta original. O que não tinha nada a ver com Guid.Parse, mas tratava de capturar Exception vs FormatException / OverflowException / etc.
Conor Gallagher
115

Os filtros de exceção agora estão disponíveis no 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 # 7.0+, você também pode combinar isso com a correspondência de padrões

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae &&
                           ae.InnerExceptions.Count > tasks.Count/2)
{
   //More than half of the tasks failed maybe..? 
}
Mat J
fonte
Esse método é preferido não apenas porque é simples e claro, mas também não precisa desbobinar a pilha se as condições não forem atendidas, o que fornece melhor desempenho e informações de diagnóstico em comparação com a repetição.
joe
74

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

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

Algumas pessoas pensam 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, esse é o único novo recurso do C # 6 que não é possível emular nas versões anteriores. Primeiro, um re-lançamento significa mais sobrecarga do que pular a captura. Segundo, 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 discussão sobre isso no CodePlex . E um exemplo mostrando a diferença .

Maniero
fonte
4
O lançamento sem exceção preserva a pilha, mas "throw ex" a substituirá.
Ivan
32

Se você não quiser usar uma ifdeclaração dentro dos catchescopos, em C# 6.0que você pode usar Exception Filtersa sintaxe que já foi apoiada pelo CLR em versões previews mas existia apenas em VB.NET/ MSIL:

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

Esse código captura o Exceptionúnico quando é um InvalidDataExceptionouArgumentNullException .

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

static int a = 8;

...

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

Observe que, ao contrário de uma ifdeclaração dentro do catchescopo da, Exception Filtersnão pode ser lançada Exceptionse, quando ocorrer , ou quando a condição não for true, a próxima catchcondição 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 true Exception Filter- 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: Captura.

E como você pode ver no MSILcódigo, o código não é traduzido para ifinstruções, mas para Filters, e Exceptionsnão pode ser jogado de dentro das áreas marcadas com Filter 1e, Filter 2mas o filtro que está lançando Exceptionfalhará, também o último valor de comparação enviado à pilha antes do endfiltercomando determinará o sucesso / falha do filtro (o Catch 1 XOR Catch 2 será executado de acordo):

Filtros de exceção MSIL

Além disso, especificamente, Guidtem o Guid.TryParsemétodo

Tamir Vered
fonte
+1 para mostrar vários filtros quando e fornecer uma explicação do que acontece quando vários filtros são usados.
Steven87vt
26

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

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

E com C # 8 como expressão de chave:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}
Fabian
fonte
3
Esta deve ser a resposta aceita em 2018 IMHO.
MemphiZ
6
A resposta de Mat J whené muito mais elegante / apropriada do que um switch.
Rgoliveira 6/11
@rgoliveira: Concordo que, para o caso solicitado, a resposta de Mat J é mais elegante e apropriada. No entanto, fica difícil ler se você tem um código diferente que deseja executar, dependendo do tipo de exceção ou se deseja realmente usar a instância da exceção. Todos esses cenários podem ser tratados igualmente com essa instrução switch.
Fabian
1
@Fabian "se você tem um código diferente que deseja executar dependendo do tipo de exceção ou se deseja realmente usar a instância da exceção", basta criar um catchbloco diferente ou precisar convertê-lo de qualquer maneira. Na minha experiência, um throw;no seu catchbloco é provavelmente um cheiro de código.
Rgoliveira 7/11
@rgoliveira: Usar um lance em um bloco de captura é bom em vários casos, veja o link . Como a instrução case realmente usa o link de correspondência de padrão, você não precisa converter se substituir o link do operador de descarte (o sublinhado) por um nome de variável. Não me entenda mal, concordo que os filtros de exceção são uma maneira mais limpa de fazê-lo, mas vários blocos de captura adicionam muitos colchetes.
Fabian
20

A resposta aceita parece aceitável, exceto que CodeAnalysis / FxCop irá reclamar do fato de estar capturando um tipo de exceção geral.

Além disso, parece que o operador "é" pode prejudicar um pouco o desempenho.

CA1800: Não transmita desnecessariamente diz "considere testar o resultado do operador 'as'", mas se você fizer isso, estará escrevendo mais código do que se capturar cada exceção separadamente.

De qualquer forma, aqui está o que eu faria:

bool exThrown = false;

try
{
    // Something
}
catch (FormatException) {
    exThrown = true;
}
catch (OverflowException) {
    exThrown = true;
}

if (exThrown)
{
    // Something else
}
Matt
fonte
19
Mas lembre-se de que não é possível repetir a exceção sem perder o rastreamento da pilha se você fizer assim. (Veja o comentário de Michael Stum para a resposta aceita)
René
2
Esse padrão pode ser melhorado armazenando a exceção (desculpe a má formatação - não consigo descobrir como inserir código nos comentários): Exceção ex = null; tente {// algo} catch (FormatException e) {ex = e; } captura (OverflowException e) {ex = e; } If (ex! = Null) {// outra coisa e lidar com ex}
Jesse Weigert
3
@JesseWeigert: 1. Você pode usar backticks para dar a um pedaço de texto uma fonte com espaçamento simples e fundo cinza claro. 2. Você ainda não poderá repetir a exceção original, incluindo o rastreamento de pilha .
22412 Oliver Oliver
2
@CleverNeologism embora possa ser verdade que o uso do isoperador possa ter um leve impacto negativo no desempenho, também é verdade que um manipulador de exceções não é o lugar para se preocupar excessivamente com a otimização do desempenho. Se seu aplicativo está gastando tanto tempo em manipuladores de exceção que a otimização de desempenho faria uma diferença real no desempenho do aplicativo, existem outros problemas de código para examinar com atenção. Dito isso, ainda não gosto dessa solução porque você perde o rastreamento de pilha e porque a limpeza é contextualmente removida da instrução catch.
Craig
3
A única vez em que o isoperador diminui o desempenho é se você executar uma asoperação posteriormente (portanto, eles qualificam a regra desnecessariamente ). Se tudo o que você está fazendo é testar o elenco sem realmente precisar executá-lo, o isoperador é exatamente o que você deseja usar.
saluce
19

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");
 }
SHM
fonte
18

Esta é uma variante da resposta de Matt (acho 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, adicione-o APÓS as 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
}
bsara
fonte
-1 Isso será executado WebId = Guid.Emtpyno caso em que nenhuma exceção foi lançada.
Sepster
4
@pspster Acho que a declaração de retorno depois de "// alguma coisa" está implícita aqui. Eu realmente não gosto da solução, mas esta é uma variante construtiva na discussão. +1 para desfazer o seu voto negativo :-)
toong
@Sepster toong está certo, presumi que, se você quisesse voltar para lá, colocaria uma ... Eu estava tentando fazer minha resposta geral o suficiente para aplicar a todas as situações, caso outras pessoas com perguntas semelhantes, mas não exatas, se beneficiassem. bem. No entanto, para uma boa medida, adicionei um returna minha resposta. Obrigado pela contribuição.
bsara
18

A resposta de Joseph Daigle é uma boa solução, mas achei a estrutura a seguir um pouco mais organizada e menos propensa a erros.

catch(Exception ex)
{   
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Existem algumas vantagens em inverter a expressão:

  • Uma declaração de retorno não é necessária
  • O código não está aninhado
  • Não há risco de esquecer as declarações 'throw' ou 'return' que na solução de Joseph estão separadas da expressão.

Pode até ser compactado em uma única linha (embora não seja muito bonita)

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Editar: a filtragem de exceção no C # 6.0 tornará a sintaxe um pouco mais limpa e traz vários outros benefícios sobre qualquer solução atual. (principalmente deixando a pilha ilesa)

Aqui está como o mesmo problema ficaria usando a sintaxe do C # 6.0:

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
Stefan T
fonte
2
+1, esta é a melhor resposta. É melhor que a resposta principal principalmente porque não há return, embora inverter a condição também seja um pouco melhor.
DCShannon
Eu nem pensei nisso. Boa captura, vou adicioná-lo à lista.
Stefan T
16

@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.

FlySwat
fonte
21
Por que não usar apenas a palavra-chave "is"?
Chris Pietschmann
29
@ Michael - Se a Microsoft introduziu, digamos, StringTooLongException derivada de FormatException, ainda é uma exceção de formato, apenas uma específica. Depende se você deseja a semântica de 'capturar exatamente esse tipo de exceção' ou 'capturar exceções que significam que o formato da sequência estava errado'.
Greg Beech
6
@ Michael - Além disso, nota que "catch (FormatException ex) tem os últimos semântica, ele vai apanhar alguma coisa derivado de FormatException.
Greg Faia
14
@Alex No. "throw" sem "ex" carrega a exceção original, incluindo o rastreamento da pilha original. Adicionar "ex" faz com que o rastreamento da pilha seja redefinido, para que você realmente receba uma exceção diferente da original. Tenho certeza que alguém pode explicar melhor do que eu. :)
Samantha Branham
13
-1: Este código é extremamente frágil - um desenvolvedor de biblioteca poderia esperar para substituir throw new FormatException();com throw new NewlyDerivedFromFormatException();sem quebrar o código usando a biblioteca, e isso será uma realidade para todos os manipulação de exceção casos, excepto quando alguém usou ==em vez de is(ou simplesmente catch (FormatException)).
21713 Sam Harwell
13

E se

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
Maurice
fonte
Isso funciona apenas se o código de captura puder ser totalmente movido para o bloco de tentativa. Mas o código de criação de imagens em que você faz várias manipulações em um objeto e um no meio falha e você deseja "redefinir" o objeto.
Michael Stum
4
Nesse caso, eu adicionaria uma função de redefinição e chamaria isso de vários blocos de captura.
Maurice
12
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
Konstantin Spirin
fonte
11

Advertido e advertido: Mais um tipo de estilo funcional.

O conteúdo do link não responde diretamente à sua pergunta, mas é trivial estendê-la para que fique assim:

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

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

(Basicamente, forneça outra Catchsobrecarga vazia que retorne)

A questão maior para isso é o porquê . Eu não acho que o custo supera o ganho aqui :)

nawfal
fonte
1
Uma possível vantagem dessa abordagem é que há uma diferença semântica entre capturar e relançar uma exceção e não capturá-la; em alguns casos, o código deve agir sobre uma exceção sem capturá-la. Isso é possível no vb.net, mas não no C #, a menos que se use um wrapper escrito no vb.net e chamado no C #.
Supercat
1
Como agir em uma exceção sem capturá-la? Eu não te entendo completamente.
Nawfal
@nawful ... usando filtro vb - função filt (ex como exceção): LOGEX (ex): return false ... então na linha de captura: captura ex quando filt (ex)
FastAl
1
@FastAl Não é isso que filtros de exceção permitem em C # 6?
HimBromBeere
@HimBromBeere sim eles são análogos diretos
FastAl
9

Atualização 15/12/2015: consulte https://stackoverflow.com/a/22864936/1718702 para obter o C # 6. É um limpador e agora padrão no idioma.

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

Eu já tinha essa extensão na minha biblioteca, originalmente escrita para outros fins, mas funcionou perfeitamente para typeverificar exceções. Além disso, parece que é mais limpo do que um monte de ||declarações. Além disso, diferentemente da resposta aceita, prefiro o tratamento explícito de exceções, para que ex is ...tenha um comportamento indesejável, pois as classes derivadas são atribuíveis aos tipos pai).

Uso

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

Extensão IsAnyOf.cs (consulte o exemplo completo de tratamento de erros 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 completo de tratamento de erros (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));
        }
    }
}

Dois exemplos de testes de unidade NUnit

O comportamento de correspondência para os Exceptiontipos é exato (ou seja, um filho NÃO é compatível com nenhum de seus tipos 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(StackOverflowException)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}
HodlDwon
fonte
1
Melhorar o idioma não é "mais elegente". Em muitos lugares, isso realmente criou um inferno de manutenção. Anos depois, muitos programadores não se orgulham do monstro que criaram. Não é o que você está acostumado a ler. Pode causar um "hein?" efeito, ou mesmo "WTFs" graves. Às vezes é confuso. A única coisa que ele faz é tornar o código muito mais difícil de entender para aqueles que precisam lidar com ele mais tarde na manutenção - apenas porque um único programador tentou ser "inteligente". Ao longo dos anos, aprendi que essas soluções "inteligentes" raramente são também as boas.
Kaii
1
ou em poucas palavras: fique com as possibilidades que a linguagem fornece nativamente. não tente substituir a semântica de um idioma, apenas porque você não gosta deles. Seus colegas (e possivelmente o futuro) me agradecerão honestamente.
Kaii
Observe também que sua solução se aproxima apenas da semântica do C # 6 when, como faria com qualquer versão do catch (Exception ex) {if (...) {/*handle*/} throw;}. O valor real de whené que o filtro é executado antes da exceção ser capturada , evitando assim a despesa / pilha corrompida de uma nova tentativa. Ele tira proveito de um recurso CLR que antes era acessível apenas ao VB e MSIL.
Marc L.
Mais elegante? Este exemplo é tão grande para um problema tão simples e o código é tão horrível que nem valia a pena dar uma olhada. Por favor, não torne esse código o problema de outra pessoa em um projeto real.
KthProg
todo o seu IsAnyOfmétodo pode ser reescrita como simplesmentep_comparisons.Contains(p_parameter)
Maksymiuk
7

Como senti que essas respostas tocaram a superfície, tentei me aprofundar um pouco mais.

Então, o que realmente queremos fazer é algo que não seja compilado, digamos:

// Won't compile... damn
public static void Main()
{
    try
    {
        throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentOutOfRangeException)
    catch (IndexOutOfRangeException) 
    {
        // ... handle
    }

A razão pela qual queremos isso é porque não queremos que o manipulador de exceções capture as coisas que precisamos posteriormente no processo. Claro, podemos pegar uma exceção e verificar com um 'se' o que fazer, mas sejamos honestos, não queremos isso de verdade. (FxCop, problemas de depuração, feiura)

Então, por que esse código não é compilado - e como podemos cortá-lo da maneira que ele irá?

Se olharmos para o código, o que realmente queremos fazer é encaminhar a chamada. No entanto, de acordo com a MS Partition II, os blocos manipuladores de exceção IL não funcionarão dessa maneira, o que, neste caso, faz sentido, porque isso implicaria que o objeto 'exceção' pode ter tipos diferentes.

Ou, para escrevê-lo em código, pedimos ao compilador que faça algo assim (bem, não está totalmente correto, mas é a coisa mais próxima possível, eu acho):

// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

A razão pela qual isso não será compilado é bastante óbvia: que tipo e valor o objeto '$ exception' teria (que são armazenados aqui nas variáveis ​​'e')? A maneira como queremos que o compilador lide com isso é observar que o tipo base comum de ambas as exceções é 'Exception', use isso para que uma variável contenha as duas exceções e, em seguida, lide apenas com as duas exceções detectadas. A maneira como isso é implementado no IL é como 'filtro', disponível no VB.Net.

Para fazê-lo funcionar em C #, precisamos de uma variável temporária com o tipo base 'Exception' correto. Para controlar o fluxo do código, podemos adicionar alguns ramos. Aqui vai:

    Exception ex;
    try
    {
        throw new ArgumentException(); // for demo purposes; won't be caught.
        goto noCatch;
    }
    catch (ArgumentOutOfRangeException e) {
        ex = e;
    }
    catch (IndexOutOfRangeException e) {
        ex = e;
    }

    Console.WriteLine("Handle the exception 'ex' here :-)");
    // throw ex ?

noCatch:
    Console.WriteLine("We're done with the exception handling.");

As desvantagens óbvias para isso são que não podemos voltar a jogar corretamente e, bem, sejamos honestos, que é uma solução feia. A feiura pode ser corrigida um pouco através da eliminação de ramificações, o que torna a solução um pouco melhor:

Exception ex = null;
try
{
    throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
    ex = e;
}
catch (IndexOutOfRangeException e)
{
    ex = e;
}
if (ex != null)
{
    Console.WriteLine("Handle the exception here :-)");
}

Isso deixa apenas o 're-lançamento'. Para que isso funcione, precisamos ser capazes de executar o tratamento dentro do bloco 'catch' - e a única maneira de fazer esse trabalho é usando um objeto 'Exception'.

Neste ponto, podemos adicionar uma função separada que lida com os diferentes tipos de exceções usando a resolução de sobrecarga ou para lidar com a exceção. Ambos têm desvantagens. Para começar, aqui está o caminho para fazê-lo com uma função auxiliar:

private static bool Handle(Exception e)
{
    Console.WriteLine("Handle the exception here :-)");
    return true; // false will re-throw;
}

public static void Main()
{
    try
    {
        throw new OutOfMemoryException();
    }
    catch (ArgumentException e)
    {
        if (!Handle(e)) { throw; }
    }
    catch (IndexOutOfRangeException e)
    {
        if (!Handle(e)) { throw; }
    }

    Console.WriteLine("We're done with the exception handling.");

E a outra solução é capturar o objeto Exception e manipulá-lo adequadamente. A tradução mais literal para isso, com base no contexto acima, é esta:

try
{
    throw new ArgumentException();
}
catch (Exception e)
{
    Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
    if (ex != null)
    {
        Console.WriteLine("Handle the exception here :-)");
        // throw ?
    }
    else 
    {
        throw;
    }
}

Então, para concluir:

  • Se não quisermos jogar novamente, podemos considerar capturar as exceções corretas e armazená-las temporariamente.
  • Se o manipulador é simples e queremos reutilizar o código, a melhor solução é provavelmente introduzir uma função auxiliar.
  • Se queremos relançar, não temos escolha a não ser colocar o código em um manipulador de captura 'Exception', que interromperá as exceções não capturadas do FxCop e do depurador.
atlaste
fonte
7

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

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

Posso capturar várias exceções ao mesmo tempo?

Em suma, não.

O que leva à próxima pergunta,

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

Dada sua amostra específica, em que o valor de retorno é barato de construir, eu gosto de seguir estas etapas:

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

Portanto, o código se parece com:

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 alguma exceção for lançada, o WebId nunca será definido como o valor semi-construído e permanecerá Guid.Empty.

Se a construção do valor de fallback for cara e a redefinição de um valor for muito mais barata, eu moveria 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);
}
Jeffrey Rennie
fonte
Isso é legal, "codificação ecológica", ou seja, você está pensando no futuro sobre sua pegada de código e dados e certificando-se de que não haja vazamentos de valores processados ​​pela metade. Bom seguir esse padrão, obrigado Jeffrey!
Tahir Khalid
6

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

Portanto, seu código se resume a 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.

No C # 6, você também tem os filtros de exceção, 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); 
}
HimBromBeere
fonte
3
"Eu me pergunto por que ninguém percebeu essa duplicação de código". - Uh o quê? O ponto principal da questão é eliminar a duplicação de código.
Mark Amery
4

Queria adicionar minha resposta curta a esta discussão já longa. Algo que não foi mencionado é a ordem de precedência das instruções de captura, mais especificamente, você precisa estar ciente do escopo de cada tipo de exceção que está tentando capturar.

Por exemplo, se você usar uma exceção "catch-all" como Exception, ela precederá todas as outras instruções de captura e, obviamente, você obterá erros de compilador; no entanto, se você reverter a ordem, poderá encadear suas instruções de captura (um pouco do anti-padrão, acho). ), você pode colocar o tipo de exceção global na parte inferior e isso capturará todas as exceções que não atenderam a níveis mais altos na sua tentativa ... bloco de captura:

            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 que as pessoas revejam este documento do MSDN:

Hierarquia de exceção

Tahir Khalid
fonte
4

Talvez tente manter seu código simples, como colocar o código comum em um método, como faria em qualquer outra parte do código que não esteja dentro de uma cláusula catch?

Por exemplo:

try
{
    // ...
}
catch (FormatException)
{
    DoSomething();
}
catch (OverflowException)
{
    DoSomething();
}

// ...

private void DoSomething()
{
    // ...
}

Como eu faria isso, tentando encontrar o padrão simples e bonito

Żubrówka
fonte
3

Observe que eu encontrei uma maneira de fazer isso, mas isso parece mais com o material do The Daily WTF :

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
Michael Stum
fonte
9
-1 voto, +5 WTF :-) Isso não deveria ter sido marcado como resposta, mas é hilário.
Aaron
1
Não importa quão simples possamos fazê-lo. Mas ele não ficou ocioso e teve sua visão para resolvê-lo. Realmente apreciado.
Maxymus 21/01
2
Entretanto, não faça isso de fato, use Filtros de exceção no C # 6 ou qualquer outra resposta - coloquei isso aqui especificamente como "Essa é uma maneira, mas é ruim e quero fazer algo melhor".
Michael Stum
Por que isso é ruim? Fiquei intrigado por você não poder usar a exceção diretamente em uma declaração de switch.
MKesper 8/06/16
3
@ MKesper vejo algumas razões pelas quais é ruim. Exige escrever os nomes de classe totalmente qualificados como literais de string, vulnerável a erros de digitação dos quais o compilador não pode salvar você. (Isso é significativo, pois em muitas lojas os casos de erro são menos testados e, portanto, é mais provável que ocorram erros triviais neles.) Ele também falhará ao corresponder a uma exceção que é uma subclasse de um dos casos especificados. E, por serem seqüências de caracteres, os casos serão perdidos por ferramentas como "Localizar todas as referências" do VS - pertinentes se você desejar adicionar uma etapa de limpeza em qualquer lugar em que uma exceção específica seja detectada.
Mark Amery
2

Vale mencionar aqui. Você pode responder às várias combinações (erro de exceção e mensagem de exceção).

Encontrei um cenário de caso de uso ao tentar converter o objeto de controle em um datagrid, com conteúdo como TextBox, TextBlock ou CheckBox. Nesse caso, a exceção retornada era a mesma, mas a mensagem variava.

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 
George
fonte
0

Quero sugerir a resposta mais curta (mais um estilo funcional ):

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

Para isso, é necessário criar várias sobrecargas do método "Catch", semelhantes ao System.Action:

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

e assim por diante quantas você desejar. Mas você precisa fazer uma vez e pode usá-lo em todos os seus projetos (ou, se você criou um pacote nuget, também podemos usá-lo).

E implementação do CatchMany:

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

ps Não coloquei verificações nulas para simplificar o código, considere adicionar validações de parâmetro.

ps2 Se você deseja retornar um valor da captura, é necessário fazer os mesmos métodos de captura, mas com retornos e Func em vez de Ação nos parâmetros.

Eugene Gorbovoy
fonte
-15

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

É simples assim !!

estudioso
fonte
3
hum. isso está derrotando o objetivo da pergunta. Ele faz essa pergunta para se livrar do código duplicado. esta resposta adiciona mais código duplicado.
James Esh
-23

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");
    }
}
Kashif
fonte
13
Este exemplo não mostra nenhum uso de filtros de exceção.
User247702
Esta é a maneira padrão de exceção de filtro em C # 6.0
Kashif
5
Veja novamente o que exatamente são os filtros de exceção. Você não está usando um filtro de exceção no seu exemplo. Há um exemplo adequado nesta resposta postada um ano antes da sua.
User247702
6
Um exemplo de filtragem de exceção seriacatch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
saluce 13/11/2015