简体   繁体   English

CIL 'fault' 子句与 C# 中的'catch' 子句有何不同?

[英]How are CIL 'fault' clauses different from 'catch' clauses in C#?

According to the CLI standard (Partition IIA, chapter 19) and the MSDN reference page for the System.Reflection.ExceptionHandlingClauseOptions enum , there are four different kinds of exception handler blocks:根据CLI 标准(第 IIA 部分,第 19 章)和System.Reflection.ExceptionHandlingClauseOptions枚举的 MSDN 参考页面,有四种不同类型的异常处理程序块:

  • catch clauses: "Catch all objects of the specified type." catch子句: “捕获指定类型的所有对象。”
  • filter clauses: "Enter handler only if filter succeeds."过滤器子句: “仅当过滤器成功时才输入处理程序。”
  • finally clauses: "Handle all exceptions and normal exit." finally子句: “处理所有异常并正常退出。”
  • fault clauses: "Handle all exceptions but not normal exit."故障子句: “处理所有异常但不正常退出。”

Given these brief explanations (cited from the CLI Standard, btw.), these should map to C# as follows:鉴于这些简短的解释(引用自 CLI 标准,顺便说一句),这些应该映射到 C#,如下所示:

  • catchcatch (FooException) { … } catchcatch (FooException) { … }
  • filter — not available in C# (but in VB.NET as Catch FooException When booleanExpression ) filter — 在 C# 中不可用(但在 VB.NET 中作为Catch FooException When booleanExpression
  • finallyfinally { … }终于—— finally { … }
  • faultcatch { … }故障catch { … }

Experiment:实验:

A simple experiment shows that this mapping is not what .NET's C# compiler really does:一个简单的实验表明,这种映射并不是 .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;
    }
}

This method returns false .此方法返回false That is, catch { … } has not been emitted as a fault clause.也就是说, catch { … }没有作为错误子句发出。

A similar experiment shows that in fact, a catch clause was emitted ( clause.Flags == ExceptionHandlingClauseOptions.Clause ), even though no exception type has been specified.一个类似的实验表明,事实上,即使没有指定异常类型,也发出了一个 catch 子句( clause.Flags == ExceptionHandlingClauseOptions.Clause )。

Questions:问题:

  1. If catch { … } really is a catch clause, then how are fault clauses different from catch clauses?如果catch { … }真的是一个 catch 子句,那么 fault 子句与 catch 子句有什么不同呢?
  2. Does the C# compiler ever output fault clauses at all? C# 编译器是否曾经输出过错误子句?

there are four different kinds of exception handler blocks:有四种不同类型的异常处理程序块:

  • catch clauses: "Catch all objects of the specified type." catch子句: “捕获指定类型的所有对象。”
  • filter clauses: "Enter handler only if filter succeeds."过滤器子句: “仅当过滤器成功时才输入处理程序。”
  • finally clauses: "Handle all exceptions and normal exit." finally子句: “处理所有异常并正常退出。”
  • fault clauses: "Handle all exceptions but not normal exit."故障子句: “处理所有异常但不正常退出。”

Given these brief explanations (cited from the CLI Standard, btw.), these should map to C# as follows:鉴于这些简短的解释(引用自 CLI 标准,顺便说一句),这些应该映射到 C#,如下所示:

  • catchcatch (FooException) { … } catchcatch (FooException) { … }
  • filter — not available in C# 1 (but in VB.NET as Catch FooException When booleanExpression ) filter — 在 C# 1 中不可用(但在 VB.NET 中作为Catch FooException When booleanExpression
  • finallyfinally { … }终于—— finally { … }
  • faultcatch { … }故障catch { … }

It's that last line where you went wrong.这是你出错的最后一行。 Read the descriptions again.再次阅读说明。 fault and finally are described practically identically. faultfinally的描述几乎相同。 The difference between them is that finally is always entered, whereas fault is only entered if control leaves the try via an exception.它们之间的区别在于, finally总是进入,而fault只有在控制通过异常离开try时才进入。 Note that this means that a catch block may have already acted.请注意,这意味着catch块可能已经执行。

If you write this in C#:如果你用 C# 写这个:

try {
    ...
} catch (SpecificException ex) {
    ...
} catch {
    ...
}

Then there is no way that the third block will be entered if control leaves the try via a SpecificException .然后,如果控制通过SpecificException离开try ,则无法进入第三个块。 That's why catch {} isn't a mapping for fault .这就是为什么catch {}不是fault的映射。


1 Since people keep mentioning this in the comments, a) This part of the answer is a quote from the original question and b) yes, when clauses have subsequently been added to C#. 1,因为不断有人在评论中提到这一点,一)答案的这部分是肯定的,原来的问题和b)报价when的条款后来被添加到C#。 It was however accurate at the time of asking and isn't what this answer focusses on answering.然而,在提问时它是准确的,并不是这个答案关注的重点。

.NET exceptions piggy-back onto the operating system's support for exceptions. .NET 异常依赖于操作系统对异常的支持。 Called Structured Exception Handling on Windows.在 Windows 上称为结构化异常处理。 Unix operating systems have something similar, signals. Unix 操作系统有类似的东西,信号。

A managed exception is a very specific case of an SEH exception.托管异常是 SEH 异常的一种非常特殊的情况。 The exception code is 0xe0434f53.异常代码是 0xe0434f53。 The last three hex pairs spell "COM", tells you something about the way .NET got started.最后三个十六进制对拼写“COM”,告诉您有关 .NET 启动方式的一些信息。

A program in general might have a stake at knowing when any exception is raised and handled, not just managed exceptions.一般而言,程序可能与知道何时引发和处理任何异常有关,而不仅仅是托管异常。 You can see this back in the MSVC C++ compiler as well.您也可以在 MSVC C++ 编译器中看到这一点。 A catch(...) clause only catches C++ exceptions. catch(...) 子句只捕获 C++ 异常。 But if you compile with the /EHa option then it catches any exception.但是,如果您使用 /EHa 选项进行编译,那么它会捕获任何异常。 Including the really nasty stuff, processor exceptions like access violations.包括真正讨厌的东西,像访问冲突这样的处理器异常。

The fault clause is the CLR's version of that, its associated block will execute for any operating system exception, not just managed ones. fault子句是 CLR 的版本,它的关联块将针对任何操作系统异常执行,而不仅仅是托管异常。 The C# and VB.NET languages do not support this, they only support exception handling for managed exceptions. C# 和 VB.NET 语言不支持这一点,它们只支持托管异常的异常处理。 But other languages may, I only know of the C++/CLI compiler emitting them.但其他语言可能,我只知道 C++/CLI 编译器发出它们。 Done for example in its version of the using statement, called "stack semantics".例如在它的using语句版本中完成,称为“堆栈语义”。

It does make sense that C++/CLI would support that, it is after all a language that strongly supports directly calling native code from managed code. C++/CLI 支持这一点确实是有道理的,毕竟它是一种强烈支持从托管代码直接调用本机代码的语言。 But not for C# and VB.NET, they only ever run unmanaged code through the pinvoke marshaller or the COM interop layer in the CLR.但不适用于 C# 和 VB.NET,它们只通过 pinvoke marshaller 或 CLR 中的 COM 互操作层运行非托管代码。 Which already sets up a 'catch-them-all' handler that translates unmanaged exceptions into managed ones.它已经设置了一个“一应俱全”的处理程序,将非托管异常转换为托管异常。 Which is the mechanism by which you get a System.AccessViolationException.这是您获得 System.AccessViolationException 的机制。

1. If catch { … } really is a catch clause, then how are fault clauses different from catch clauses? 1.如果catch { … }真的是catch 子句,那么fault 子句与catch 子句有什么不同?

The C# compiler (at least the one that ships with .NET) actually appears to compile catch { … } as if it were really catch (object) { … } . C# 编译器(至少是 .NET 附带的编译器)实际上似乎编译catch { … }就好像它真的是catch (object) { … } This can be shown with the code below.这可以用下面的代码显示。

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

That method returns typeof(object) .该方法返回typeof(object)

So conceptually, a fault handler is is similar to a catch { … } ;所以,从概念上讲,一个故障处理程序 类似于一个catch { … } ; however, the C# compiler never generates code for that exact construct but pretends that it is a catch (object) { … } , which is conceptually a catch clause.然而,C# 编译器从不为那个确切的构造生成代码,而是假装它是一个catch (object) { … } ,它在概念上是一个 catch 子句。 Thus a catch clause gets emitted.因此会发出一个 catch 子句。

Side note: Jeffrey Richter's book "CLR via C#" has some related information (on pp. 472–474): Namely that the CLR allows any value to be thrown, not just Exception objects.旁注: Jeffrey Richter 的“CLR via C#”一书有一些相关信息(第 472-474 页):即 CLR 允许抛出任何值,而不仅仅是Exception对象。 However, starting with CLR version 2, non- Exception values are automatically wrapped in a RuntimeWrappedException object.但是,从 CLR 版本 2 开始,非Exception值会自动包装在RuntimeWrappedException对象中。 So it seems somewhat surprising that C# would transform catch into catch (object) instead of catch (Exception) .因此,C# 将catch转换为catch (object)而不是catch (Exception)似乎有些令人惊讶。 There is however a reason for this: The CLR can be told not to wrap non- Exception values by applying a [assembly: RuntimeCompatibility(WrapNonExceptionThrows = false)] attribute.然而,这样做是有原因的:通过应用[assembly: RuntimeCompatibility(WrapNonExceptionThrows = false)]属性,可以告诉 CLR 不要包装非Exception值。

By the way, the VB.NET compiler, unlike the C# compiler, translates Catch to Catch anonymousVariable As Exception .顺便说一下,与 C# 编译器不同,VB.NET 编译器将Catch转换为Catch anonymousVariable As Exception


2. Does the C# compiler ever output fault clauses at all? 2. C# 编译器是否曾经输出过错误子句?

It obviously doesn't emit fault clauses for catch { … } .它显然不会为catch { … }发出错误子句。 However, Bart de Smet's blog post "Reader challenge – fault handlers in C#" suggests that the C# compiler does produce fault clauses in certain circumstances.但是,Bart de Smet 的博客文章“读者挑战 – C# 中的错误处理程序”表明 C# 编译器在某些情况下确实会生成错误子句。

As people have pointed out, generally speaking the C# compiler doesn't generate fault handlers.正如人们所指出的,一般来说 C# 编译器不会生成错误处理程序。 However, stakx linked to Bart de Smet's blog post on how to get the C# compiler to generate a fault handler.但是,stakx 链接到Bart de Smet关于如何让 C# 编译器生成故障处理程序的博客文章

C# does use fault handlers to implement using statements that are inside iterator blocks. C# 确实使用错误处理程序来实现迭代器块内的 using 语句。 Eg the following C# code will cause the compiler to use a fault clause:例如,以下 C# 代码将导致编译器使用错误子句:

public IEnumerable<string> GetSomeEnumerable()
{
    using (Disposable.Empty)
    {
        yield return DoSomeWork();
    }
}

Decompiling the generated assembly with dotPeek and the "Show compiler-generated code" option on, you can see the fault clause:使用 dotPeek 和“显示编译器生成的代码”选项反编译生成的程序集,您可以看到错误子句:

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

Where normally a using statement would map to a try/finally block, this doesn't make sense for iterator blocks - a try/finally would Dispose after the first value is generated.通常 using 语句会映射到 try/finally 块,这对迭代器块没有意义 - try/finally 将在生成第一个值后处理。

But if DoSomeWork throws an exception, you do want to Dispose.但是,如果 DoSomeWork 抛出异常,您确实想要 Dispose。 So a fault handler is useful here.因此,故障处理程序在这里很有用。 It will call Dispose only in the case where an exception occurs, and allow the exception to bubble up.它只会在发生异常的情况下调用 Dispose,并允许异常冒泡。 Conceptually this is similar to a catch block that disposes and then re-throws.从概念上讲,这类似于处理然后重新抛出的 catch 块。

A fault block would be equivalent to saying:故障块相当于说:

bool success;
try
{
    success = false;
    ... do stuff
    success = true; // Also include this immediately before any 'return'    
}
finally
{
    if (!success)
    {
        ... do "fault" stuff here
    }
}

Note that this is somewhat different semantically from catch-and-rethrow.请注意,这在语义上与 catch-and-rethrow 有所不同。 Among other things, with the implementation above, if an exception occurs and the stack trace is reporting line numbers, it will include the number of the line in ...do stuff where the exception occurred.除此之外,在上面的实现中,如果发生异常并且堆栈跟踪正在报告行号,它将包括在...do stuff发生异常的地方的行号。 By contrast, when using catch-and-rethrow, the stack trace would report the line number of the rethrow.相比之下,当使用捕获并重新抛出时,堆栈跟踪将报告重新抛出的行号。 If ...do stuff includes two or more calls to foo , and one of those calls throws an exception, knowing the line number of the call that failed could be helpful, but catch-and-rethrow would lose that information.如果...do stuff包括两个或多个对foo调用,并且其中一个调用抛出异常,则知道失败的调用的行号可能会有所帮助,但捕获并重新抛出会丢失该信息。

The biggest problems with the above implementation are that one must manually add success = true;上述实现的最大问题是必须手动添加success = true; to every place in the code that could exit the try block, and that there's no way for the finally block to know what exception may be pending.到代码中可能退出try块的每个地方,并且finally块无法知道可能挂起的异常。 If I had my druthers, there would be a finally (Exception ex) statement which would set Ex to the exception that caused the try block to exit (or null if the block exited normally).如果我有我的 druthers,将会有一个finally (Exception ex)语句,它将Ex设置为导致 try 块退出的异常(如果块正常退出,则为null )。 Not only would that eliminate the need to manually set the 'success' flag, but it would allow sensible handling of the case where an exception occurs in cleanup code.这不仅可以消除手动设置“成功”标志的需要,而且可以合理处理清理代码中发生异常的情况。 In such a situation, one should not obscure the cleanup exception (even if the original exception would normally represent a condition the calling code was expecting, the cleanup failure probably represents a condition it wasn't) but one probably doesn't want to lose the original exception either (since it probably contains clues as to why the cleanup failed).在这种情况下,不应掩盖清理异常(即使原始异常通常表示调用代码所期望的条件,但清理失败可能表示它不是的条件)但可能不想丢失原始异常(因为它可能包含有关清理失败原因的线索)。 Allowing a finally block to know why it was entered, and including an extended version of IDisposable via which a using block could make such information available to cleanup code, would make it possible to resolve such situations cleanly.允许finally块知道它为什么被输入,并包括IDisposable的扩展版本,通过它using块可以使此类信息可用于清理代码,将使干净地解决此类情况成为可能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM