Control.Invoke 展开外部异常并传播内部异常

Control.Invoke unwraps the outer exception and propagates the inner exception instead

下面的 MessageBox.Show 调用显示 "Inner"。这是一个错误吗?

private void Throw()
{
    Invoke(new Action(() =>
    {
        throw new Exception("Outer", new Exception("Inner"));
    }));
}

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        Throw();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message); // Shows "Inner"
    }
}

我查看了 System.Windows.Forms.Control 的参考源,处理 Invoke 的代码如下所示:

try {
    InvokeMarshaledCallback(current);
}
catch (Exception t) {
    current.exception = t.GetBaseException();
}

GetBaseException:

public virtual Exception GetBaseException() 
{
    Exception inner = InnerException;
    Exception back = this;

    while (inner != null) {
        back = inner;
        inner = inner.InnerException;
    }

    return back;
}

显然是设计使然。源代码中的评论没有解释他们为什么这样做。

编辑:一些现在已经消失的网站声称这条评论来自微软的一个人:

Based on the winform comfirmation in the record, our analysis is correct of the root cause and this behavior is intended. The reason was to prevent the user from seeing too much of the Windows.Forms internal mechanisms. This is because the winform's default error dialog also leverages Application.ThreadException to show the exception details. .Net Winform team trims the other exceptions information so that the default error dialog will not display all the details to the end user.

Also, some MSFTs have sugguested to change this behavior. However, .Net Winform team thinks that changing the exception to throw is a breaking change and for this reason WinForms will keep sending the innermost exception to the Application.ThreadException handler.

OP 似乎对 work-around 不感兴趣。不管怎样,这是我的:

public static object InvokeCorrectly(this Control control, Delegate method, params object[] args) {
    Exception failure = null;
    var result = control.Invoke(new Func<object>(() => {
        try {
            return method.DynamicInvoke(args);
        } catch (TargetInvocationException ex) {
            failure = ex.InnerException;
            return default;
        }
    }));
    if (failure != null) {
        throw failure;
    }
    return result;
}