不鼓励只捕获System.Exception。相反,只应捕获“已知”异常。

现在,这有时会导致不必要的重复代码,例如:

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

我想知道:是否有一种方法可以捕获两个异常,并且只调用WebId=Guid.Empty调用一次?

给定的示例相当简单,因为它只是一个GUID。但是想象一下,在代码中,您多次修改一个对象,如果其中一个操作预期失败,您希望“重置”该对象。然而,如果有意外的异常,我仍然想把它推得更高。


当前回答

我想建议最简短的答案(一种更实用的方式):

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

为此,您需要创建几个“Catch”方法重载,类似于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));
    }

你想多少就多少。但是你需要做一次,你可以在所有项目中使用它(或者,如果你创建了一个nuget包,我们也可以使用它)。

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

p.s.为了代码的简单性,我没有进行空检查,请考虑添加参数验证。

第2页如果要从catch返回值,则必须执行相同的catch方法,但在参数中使用return和Func而不是Action。

其他回答

如果您不想在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方法。

注意,我确实找到了一种方法,但这看起来更像是《每日WTF》的素材:

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

使用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()
    };
}

想在这条已经很长的线索上加上我的简短回答。还没有提到的是catch语句的优先顺序,更具体地说,您需要了解您试图捕获的每种类型的异常的范围。

例如,如果您使用“catch-all”异常作为异常,它将优先于所有其他catch语句,您显然会遇到编译器错误。但是,如果您颠倒了catch语句的顺序(我认为这有点反模式),您可以将catch-allException类型放在底部,这将捕获任何在尝试中不适合更高级别的异常。。捕捉块:

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

我强烈建议大家阅读此MSDN文档:

异常层次结构

为了完整起见,自.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参数的解构时,这个无用答案的下一个无用更新就来了