不鼓励只捕获System.Exception。相反,只应捕获“已知”异常。
现在,这有时会导致不必要的重复代码,例如:
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
我想知道:是否有一种方法可以捕获两个异常,并且只调用WebId=Guid.Empty调用一次?
给定的示例相当简单,因为它只是一个GUID。但是想象一下,在代码中,您多次修改一个对象,如果其中一个操作预期失败,您希望“重置”该对象。然而,如果有意外的异常,我仍然想把它推得更高。
如果您不想在catch范围内使用If语句,在C#6.0中,您可以使用CLR在预览版本中已经支持但仅存在于VB.NET/MSIL中的异常过滤器语法:
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
}
只有当异常为InvalidDataException或ArgumentNullException时,此代码才会捕获该异常。
实际上,你可以在when子句中放入基本上任何条件:
static int a = 8;
...
catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
Console.WriteLine("Catch");
}
请注意,与catch范围内的if语句不同,异常过滤器不能抛出异常,当抛出异常时,或者当条件不为真时,将计算下一个catch条件:
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");
}
输出:常规捕获。
当存在多个真正的异常筛选器时,将接受第一个:
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");
}
输出:捕获。
正如您在MSIL中看到的,代码不是转换为if语句,而是转换为筛选器,并且异常不能从标记为筛选器1和筛选器2的区域中抛出,但是抛出异常的筛选器将失败,在endfilter命令之前推送到堆栈的最后一个比较值也将决定过滤器的成功/失败(Catch 1 XOR Catch 2将相应地执行):
此外,特别是Guid具有Guid.TryParse方法。
使用C#7,Michael Stum的答案可以得到改进,同时保持switch语句的可读性:
catch (Exception ex)
{
switch (ex)
{
case FormatException _:
case OverflowException _:
WebId = Guid.Empty;
break;
default:
throw;
}
}
由于Orace注释,这可以通过省略丢弃变量而用C#8来简化:
catch (Exception ex)
{
switch (ex)
{
case FormatException:
case OverflowException:
WebId = Guid.Empty;
break;
default:
throw;
}
}
使用C#8作为开关表达式:
catch (Exception ex)
{
WebId = ex switch
{
_ when ex is FormatException || ex is OverflowException => Guid.Empty,
_ => throw ex
};
}
正如内切米娅·霍夫曼指出的那样。后一个示例将导致堆栈丢失。这可以通过使用Jürgen Steinblock描述的扩展方法来防止,以在投掷前捕获堆叠:
catch (Exception ex)
{
WebId = ex switch
{
_ when ex is FormatException || ex is OverflowException => Guid.Empty,
_ => throw ex.Capture()
};
}
public static Exception Capture(this Exception ex)
{
ExceptionDispatchInfo.Capture(ex).Throw();
return ex;
}
这两种样式都可以通过C#9的模式匹配增强来简化:
catch (Exception ex)
{
switch (ex)
{
case FormatException or OverflowException:
WebId = Guid.Empty;
break;
default:
throw;
}
}
catch (Exception ex)
{
WebId = ex switch
{
_ when ex is FormatException or OverflowException => Guid.Empty,
_ => throw ex.Capture()
};
}
为了完整起见,自.NET 4.0以来,代码可以重写为:
Guid.TryParse(queryString["web"], out WebId);
TryParse从不抛出异常,如果格式错误,则返回false,将WebId设置为Guid.Empty。
由于C#7,您可以避免在单独的行中引入变量:
Guid.TryParse(queryString["web"], out Guid webId);
您还可以创建用于解析返回元组的方法,这些方法在.NET Framework 4.6版之前尚不可用:
(bool success, Guid result) TryParseGuid(string input) =>
(Guid.TryParse(input, out Guid result), result);
并像这样使用它们:
WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;
当在C#12中实现out参数的解构时,这个无用答案的下一个无用更新就来了
如果您不想在catch范围内使用If语句,在C#6.0中,您可以使用CLR在预览版本中已经支持但仅存在于VB.NET/MSIL中的异常过滤器语法:
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
}
只有当异常为InvalidDataException或ArgumentNullException时,此代码才会捕获该异常。
实际上,你可以在when子句中放入基本上任何条件:
static int a = 8;
...
catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
Console.WriteLine("Catch");
}
请注意,与catch范围内的if语句不同,异常过滤器不能抛出异常,当抛出异常时,或者当条件不为真时,将计算下一个catch条件:
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");
}
输出:常规捕获。
当存在多个真正的异常筛选器时,将接受第一个:
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");
}
输出:捕获。
正如您在MSIL中看到的,代码不是转换为if语句,而是转换为筛选器,并且异常不能从标记为筛选器1和筛选器2的区域中抛出,但是抛出异常的筛选器将失败,在endfilter命令之前推送到堆栈的最后一个比较值也将决定过滤器的成功/失败(Catch 1 XOR Catch 2将相应地执行):
此外,特别是Guid具有Guid.TryParse方法。
约瑟夫·戴格尔的答案是一个很好的解决方案,但我发现下面的结构更整洁,更不容易出错。
catch(Exception ex)
{
if (!(ex is SomeException || ex is OtherException)) throw;
// Handle exception
}
反转表达式有几个优点:
return语句不是必需的代码未嵌套不存在忘记“抛出”或“返回”语句的风险,这些语句在Joseph的解决方案中与表达式分离。
它甚至可以压缩成一行(虽然不是很漂亮)
catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;
// Handle exception
}
编辑:C#6.0中的异常过滤将使语法更加简洁,并比任何当前解决方案都有许多其他好处。(最值得注意的是,未损坏堆栈)
下面是使用C#6.0语法时相同问题的外观:
catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
// Handle exception
}