CIL的"错误"条款与C#中的"catch"条款有何不同?

sta*_*ica 17 c# exception-handling system.reflection

根据CLI标准(分区IIA,第19章)和System.Reflection.ExceptionHandlingClauseOptions枚举的MSDN参考页面,有四种不同的异常处理程序块:

  • catch子句:"捕获指定类型的所有对象."
  • filter子句:"仅在过滤器成功时输入处理程序".
  • 最后条款:"处理所有异常和正常退出."
  • fault子句:"处理所有异常,但不能正常退出."

鉴于这些简短的解释(引自CLI标准,顺便说一句),这些应该映射到C#,如下所示:

  • 抓住 -catch (FooException) { … }
  • 过滤器 - 在C#中不可用(但在VB.NET中Catch FooException When booleanExpression)
  • 终于 -finally { … }
  • 错误 -catch { … }

实验:

一个简单的实验表明,这种映射不是.NET的C#编译器真正做的事情:

// using System.Linq;
// using System.Reflection;

static bool IsCatchWithoutTypeSpecificationEmittedAsFaultClause()
{
    try
    {
        return MethodBase
               .GetCurrentMethod()
               .GetMethodBody()
               .ExceptionHandlingClauses
               .Any(clause => clause.Flags == ExceptionHandlingClauseOptions.Fault);
    }
    catch // <-- this is what the above code is inspecting
    {
        throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

此方法返回false.也就是说,catch { … }没有作为故障条款发出.

类似的实验表明,事实上clause.Flags == ExceptionHandlingClauseOptions.Clause,即使没有指定异常类型,也会发出catch子句().

问题:

  1. 如果catch { … }确实是一个catch子句,那么fault子句如何与catch子句不同?
  2. C#编译器是否曾输出错误条款?

Dam*_*ver 13

有四种不同的异常处理程序块:

  • catch子句:"捕获指定类型的所有对象."
  • filter子句:"仅在过滤器成功时输入处理程序".
  • 最后条款:"处理所有异常和正常退出."
  • fault子句:"处理所有异常,但不能正常退出."

鉴于这些简短的解释(引自CLI标准,顺便说一句),这些应该映射到C#,如下所示:

  • 抓住 -catch (FooException) { … }
  • 过滤器 - 在C#中不可用(但在VB.NET中Catch FooException When booleanExpression)
  • 终于 -finally { … }
  • 错误 -catch { … }

这是你出错的最后一行.再次阅读说明.fault并且finally几乎相同地描述.它们之间的区别在于finally始终输入,而fault只有在控制离开try通道异常时才输入.请注意,这意味着catch块可能已经采取了行动.

如果你用C#写这个:

try {
    ...
} catch (SpecificException ex) {
    ...
} catch {
    ...
}
Run Code Online (Sandbox Code Playgroud)

如果控制离开try通孔a,则无法输入第三个块SpecificException.这就是为什么catch {}不是映射fault.


Han*_*ant 12

.NET异常捎带到操作系统的异常支持上.在Windows上称为结构化异常处理.Unix操作系统有类似的信号.

托管异常是SEH异常的一个非常具体的案例.异常代码是0xe0434f53.最后三个十六进制对拼写"COM",告诉你一些关于.NET开始的方式.

一般而言,程序可能会知道何时引发和处理任何异常,而不仅仅是托管异常.您也可以在MSVC C++编译器中看到这一点.catch(...)子句只捕获C++异常.但是如果使用/ EHa选项进行编译,则会捕获任何异常.包括真正令人讨厌的东西,处理器异常,如访问违规.

故障子句是CLR的版本,在其相应的区块将执行任何操作系统异常,而不仅仅是管理的.C#和VB.NET语言不支持此功能,它们仅支持托管异常的异常处理.但是其他语言可能,我只知道发出它们的C++/CLI编译器.例如,在其using语句的版本中,称为"堆栈语义".

确实C++/CLI支持它,它毕竟是一种强烈支持从托管代码直接调用本机代码的语言.但是对于C#和VB.NET,它们只能通过pinvoke marshaller或CLR中的COM互操作层运行非托管代码.其中已经建立了一个"捕获所有"的处理程序,可以将非托管异常转换为托管异常.这是获取System.AccessViolationException的机制.

  • 是的,它被称为"C++ Interop".以前IJW(它只是工作).没有什么特别激烈的,它使它真的很快,它只是在堆栈上写一个cookie,防止GC冒险进入非托管堆栈帧. (3认同)

sta*_*ica 10

1.如果catch { … }确实是一个catch子句,那么错误条款与catch子句有何不同?

C#编译器(至少是.NET附带的编译器)实际上看起来catch { … }像是真的一样编译 catch (object) { … }.这可以通过以下代码显示.

// using System;
// using System.Linq;
// using System.Reflection;

static Type GetCaughtTypeOfCatchClauseWithoutTypeSpecification()
{
    try
    {
        return MethodBase
               .GetCurrentMethod()
               .GetMethodBody()
               .ExceptionHandlingClauses
               .Where(clause => clause.Flags == ExceptionHandlingClauseOptions.Clause)
               .Select(clause => clause.CatchType)
               .Single();
    }
    catch // <-- this is what the above code is inspecting
    {
        throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

该方法返回typeof(object).

所以,从概念上讲,一个故障处理程序 是 类似于一个catch { … }; 但是,C#编译器从不为该确切的构造生成代码,但假装它是一个catch (object) { … },在概念上它是一个catch子句.因此会释放一个catch子句.

旁注: Jeffrey Richter的书"CLR via C#"有一些相关信息(第472-474页):即CLR允许抛出任何值,而不仅仅是Exception对象.但是,从CLR版本2开始,非Exception值将自动包装在RuntimeWrappedException对象中.所以C#会转变catchcatch (object)而不是catch (Exception).但是有一个原因:可以告诉CLR不要Exception通过应用[assembly: RuntimeCompatibility(WrapNonExceptionThrows = false)]属性来包装非值.

顺便说一句,与C#编译器不同,VB.NET编译器转换CatchCatch anonymousVariable As Exception.


2. C#编译器是否曾输出错误条款?

它显然不会发出错误条款catch { … }.但是,Bart de Smet的博客文章"读者挑战 - C#中的错误处理程序"表明C#编译器在某些情况下产生错误条款.


Nia*_*ton 6

正如人们所指出的,一般来说C#编译器不会生成错误处理程序.但是,stakx与Bart de Smet的博客文章有关如何让C#编译器生成错误处理程序.

C#确实使用错误处理程序来实现迭代器块内的语句.例如,以下C#代码将导致编译器使用fault子句:

public IEnumerable<string> GetSomeEnumerable()
{
    using (Disposable.Empty)
    {
        yield return DoSomeWork();
    }
}
Run Code Online (Sandbox Code Playgroud)

使用dotPeek和"显示编译器生成的代码"选项反编译生成的程序集,可以看到fault子句:

bool IEnumerator.MoveNext()
{
    try
    {
        switch (this.<>1__state)
        {
        case 0:
            this.<>1__state = -1;
            this.<>7__wrap1 = Disposable.Empty;
            this.<>1__state = 1;
            this.<>2__current = this.<>4__this.DoSomeWork();
            this.<>1__state = 2;
            return true;
        case 2:
            this.<>1__state = 1;
            this.<>m__Finally2();
            break;
        }
        return false;
    }
    __fault
    {
        this.System.IDisposable.Dispose();
    }
}
Run Code Online (Sandbox Code Playgroud)

通常,using语句将映射到try/finally块,这对迭代器块没有意义 - 在生成第一个值之后,try/finally会Dispose.

但是如果DoSomeWork抛出异常,你确实想要Dispose.所以故障处理程序在这里很有用.它只会在发生异常的情况下调用Dispose,并允许异常冒泡.从概念上讲,这类似于处理然后重新抛出的catch块.