C# 如何确定给定方法可以抛出哪些异常?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/986180/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 04:56:25  来源:igfitidea点击:

How can I determine which exceptions can be thrown by a given method?

c#exceptionerror-handling

提问by Jamey McElveen

My question is really the same as this one "Finding out what exceptions a method might throw in C#". However, I would really like to know if anyone knows of a way to determine the stack of all the exceptions that may be thrown by a given method. I am hoping for a tool or utility that I can analyze code at compile time or through reflection like FxCop, StyleCop, or NCover. I do not need this information at run time I just want to make sure we are trapping exceptions and logging them correctly in out code.

我的问题与“找出方法可能在 C# 中抛出的异常”这一问题非常相似。但是,我真的很想知道是否有人知道一种方法来确定给定方法可能抛出的所有异常的堆栈。我希望有一种工具或实用程序可以在编译时或通过反射分析代码,例如 FxCop、StyleCop 或 NCover。我在运行时不需要这些信息,我只想确保我们正在捕获异常并将它们正确地记录在输出代码中。

We are currently trapping the exceptions that we know about and logging all the wild cards. This does work well; however, i was just hoping someone has used or knows of a tool that can discover this information.

我们目前正在捕获我们知道的异常并记录所有通配符。这确实有效;然而,我只是希望有人使用或知道可以发现这些信息的工具。

采纳答案by Noldorin

Following up to my previous answer, I've managed to create a basic exception finder. It utilises a reflection-based ILReaderclass, available hereon Haibo Luo's MSDN blog. (Just add a reference to the project.)

按照我之前的回答,我已经设法创建了一个基本的异常查找器。它采用基于反射的ILReader类,可以在这里上海博罗的MSDN博客。(只需添加对项目的引用。)

Updates:

更新:

  1. Now handles local variables and the stack.
    • Correctly detects exceptions returned from method calls or fields and later thrown.
    • Now handles stack pushes/pops fully and appropiately.
  1. 现在处理局部变量和堆栈。
    • 正确检测从方法调用或字段返回并随后抛出的异常。
    • 现在可以完全适当地处理堆栈推送/弹出。

Here is the code, in full. You simply want to use the GetAllExceptions(MethodBase)method either as an extension or static method.

这是完整的代码。您只想将该GetAllExceptions(MethodBase)方法用作扩展或静态方法。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using ClrTest.Reflection;

public static class ExceptionAnalyser
{
    public static ReadOnlyCollection<Type> GetAllExceptions(this MethodBase method)
    {
        var exceptionTypes = new HashSet<Type>();
        var visitedMethods = new HashSet<MethodBase>();
        var localVars = new Type[ushort.MaxValue];
        var stack = new Stack<Type>();
        GetAllExceptions(method, exceptionTypes, visitedMethods, localVars, stack, 0);

        return exceptionTypes.ToList().AsReadOnly();
    }

    public static void GetAllExceptions(MethodBase method, HashSet<Type> exceptionTypes,
        HashSet<MethodBase> visitedMethods, Type[] localVars, Stack<Type> stack, int depth)
    {
        var ilReader = new ILReader(method);
        var allInstructions = ilReader.ToArray();

        ILInstruction instruction;
        for (int i = 0; i < allInstructions.Length; i++)
        {
            instruction = allInstructions[i];

            if (instruction is InlineMethodInstruction)
            {
                var methodInstruction = (InlineMethodInstruction)instruction;

                if (!visitedMethods.Contains(methodInstruction.Method))
                {
                    visitedMethods.Add(methodInstruction.Method);
                    GetAllExceptions(methodInstruction.Method, exceptionTypes, visitedMethods,
                        localVars, stack, depth + 1);
                }

                var curMethod = methodInstruction.Method;
                if (curMethod is ConstructorInfo)
                    stack.Push(((ConstructorInfo)curMethod).DeclaringType);
                else if (method is MethodInfo)
                    stack.Push(((MethodInfo)curMethod).ReturnParameter.ParameterType);
            }
            else if (instruction is InlineFieldInstruction)
            {
                var fieldInstruction = (InlineFieldInstruction)instruction;
                stack.Push(fieldInstruction.Field.FieldType);
            }
            else if (instruction is ShortInlineBrTargetInstruction)
            {
            }
            else if (instruction is InlineBrTargetInstruction)
            {
            }
            else
            {
                switch (instruction.OpCode.Value)
                {
                    // ld*
                    case 0x06:
                        stack.Push(localVars[0]);
                        break;
                    case 0x07:
                        stack.Push(localVars[1]);
                        break;
                    case 0x08:
                        stack.Push(localVars[2]);
                        break;
                    case 0x09:
                        stack.Push(localVars[3]);
                        break;
                    case 0x11:
                        {
                            var index = (ushort)allInstructions[i + 1].OpCode.Value;
                            stack.Push(localVars[index]);
                            break;
                        }
                    // st*
                    case 0x0A:
                        localVars[0] = stack.Pop();
                        break;
                    case 0x0B:
                        localVars[1] = stack.Pop();
                        break;
                    case 0x0C:
                        localVars[2] = stack.Pop();
                        break;
                    case 0x0D:
                        localVars[3] = stack.Pop();
                        break;
                    case 0x13:
                        {
                            var index = (ushort)allInstructions[i + 1].OpCode.Value;
                            localVars[index] = stack.Pop();
                            break;
                        }
                    // throw
                    case 0x7A:
                        if (stack.Peek() == null)
                            break;
                        if (!typeof(Exception).IsAssignableFrom(stack.Peek()))
                        {
                            //var ops = allInstructions.Select(f => f.OpCode).ToArray();
                            //break;
                        }
                        exceptionTypes.Add(stack.Pop());
                        break;
                    default:
                        switch (instruction.OpCode.StackBehaviourPop)
                        {
                            case StackBehaviour.Pop0:
                                break;
                            case StackBehaviour.Pop1:
                            case StackBehaviour.Popi:
                            case StackBehaviour.Popref:
                            case StackBehaviour.Varpop:
                                stack.Pop();
                                break;
                            case StackBehaviour.Pop1_pop1:
                            case StackBehaviour.Popi_pop1:
                            case StackBehaviour.Popi_popi:
                            case StackBehaviour.Popi_popi8:
                            case StackBehaviour.Popi_popr4:
                            case StackBehaviour.Popi_popr8:
                            case StackBehaviour.Popref_pop1:
                            case StackBehaviour.Popref_popi:
                                stack.Pop();
                                stack.Pop();
                                break;
                            case StackBehaviour.Popref_popi_pop1:
                            case StackBehaviour.Popref_popi_popi:
                            case StackBehaviour.Popref_popi_popi8:
                            case StackBehaviour.Popref_popi_popr4:
                            case StackBehaviour.Popref_popi_popr8:
                            case StackBehaviour.Popref_popi_popref:
                                stack.Pop();
                                stack.Pop();
                                stack.Pop();
                                break;
                        }

                        switch (instruction.OpCode.StackBehaviourPush)
                        {
                            case StackBehaviour.Push0:
                                break;
                            case StackBehaviour.Push1:
                            case StackBehaviour.Pushi:
                            case StackBehaviour.Pushi8:
                            case StackBehaviour.Pushr4:
                            case StackBehaviour.Pushr8:
                            case StackBehaviour.Pushref:
                            case StackBehaviour.Varpush:
                                stack.Push(null);
                                break;
                            case StackBehaviour.Push1_push1:
                                stack.Push(null);
                                stack.Push(null);
                                break;
                        }

                        break;
                }
            }
        }
    }
}

To summarise, this algorithm recursively enumerates (depth-first) any methods called within the specified one, by reading the CIL instructions (as well as keeping track of methods already visited). It maintains a single list of collections that can be thrown using a HashSet<T>object, which is returned at the end. It additionally maintains an array of local variables and a stack, in order to keep track of exceptions that aren't thrown immediately after they are created.

总而言之,该算法通过读取 CIL 指令(以及跟踪已经访问过的方法)递归地枚举(深度优先)在指定方法中调用的任何方法。它维护可以使用HashSet<T>对象抛出的单个集合列表,该对象在最后返回。它还维护一个局部变量数组和一个堆栈,以便跟踪在创建后没有立即抛出的异常。

Of course, this code isn't infallible in it's current state. There are a few improvements that I need to make for it to be robust, namely:

当然,此代码在当前状态下并非绝对可靠。我需要做一些改进才能使其健壮,即:

  1. Detect exceptions that aren't thrown directly using an exception constructor. (i.e. The exception is retrieved from a local variable or a method call.)
  2. Support exceptions popped off the stack then later pushed back on.
  3. Add flow-control detection. Try-catch blocks that handle any thrown exception should remove the appropiate exception from the list, unless a rethrowinstruction is detected.
  1. 使用异常构造函数检测未直接抛出的异常。(即从局部变量或方法调用中检索异常。)
  2. 支持异常从堆栈中弹出然后再推回。
  3. 添加流量控制检测。除非rethrow检测到指令,否则处理任何抛出异常的 Try-catch 块应从列表中删除适当的异常。

Apart from that, I believe the code is reasonablycomplete. It may take a bit more investigation before I figure out exactly how to do the flow-control detection (though I believe I can see how it operates at the IL-level now).

除此之外,我相信代码相当完整。在我弄清楚如何进行流量控制检测之前可能需要更多的调查(尽管我相信我现在可以看到它是如何在 IL 级别运行的)。

These functions could probably be turned into an entire library if one was to create a full-featured "exception analyser", but hopefully this will at least provide a sound starting point for such a tool, if not already good enough in its current state.

如果要创建一个功能齐全的“异常分析器”,这些函数可能会变成一个完整的库,但希望这至少能为这样的工具提供一个良好的起点,如果在当前状态下还不够好。

Anyway, hope that helps!

无论如何,希望有帮助!

回答by James

My methodology for this type of situation is to handle all the exceptions that I want to and then override the UnhandledException event of the application to log any other's that I don't know about. Then if I come up against any that I think I could possibly resolve then I would just update accordingly.

对于这种情况,我的方法是处理我想要的所有异常,然后覆盖应用程序的 UnhandledException 事件以记录我不知道的任何其他异常。然后,如果我遇到任何我认为可以解决的问题,那么我会相应地进行更新。

Hope that helps!

希望有帮助!

回答by Derek Ekins

Unlike java C# does not have the concept of checked exceptions.

不像java C#没有检查异常的概念。

On a macro level you should catch everything and log or inform the user of the error. When you know about the specific exceptions a method may raise then definetly handle that appropriately but be sure to let any other exceptions bubble up (preferably) or log them, otherwise you will have bugs that you will not be able to find and generally make life miserable for anyone hired to help reduce the bug list - have been there, not fun! :)

在宏观层面上,您应该捕获所有内容并记录或通知用户错误。当您了解某个方法可能引发的特定异常时,然后明确地对其进行适当处理,但一定要让任何其他异常冒泡(最好)或记录它们,否则您将遇到无法找到的错误,并且通常会导致错误对于任何受雇来帮助减少错误列表的人来说都是悲惨的 - 一直在那里,不好玩!:)

回答by denis phillips

John Robbins had a series of articles on creating FxCop rules including an MSDN articlethat would indicate which exceptions were thrown. This was to warn about missing XML documentation for the exceptions but the idea would be the same.

John Robbins 发表了一系列关于创建 FxCop 规则的文章,其中包括一篇MSDN 文章,该文章将指出抛出哪些异常。这是为了警告缺少异常的 XML 文档,但想法是相同的。

回答by Noldorin

I'm very doubtful there is any (at least straightforward) way to do this in C#. Saying that, I do have an idea that maywork, so read on please...

我非常怀疑在 C# 中是否有任何(至少是直接的)方法可以做到这一点。话虽如此,我确实有一个可能可行的想法,所以请继续阅读......

Firstly, it's worth noting that doing a brute-force search using a huge number of permutations of arguments clearly is not feasible. Even having prior knowledge of parameter types (which I don't believe is desirable in your situation), the task essentially reduces to the halting problemin the general case, since you don't know the function will terminate given certian parameters. Ideally, exceptions should stop this, but it isn't always the case of course.

首先,值得注意的是,使用大量参数排列进行蛮力搜索显然是不可行的。即使有参数类型的先验知识(我认为这在您的情况下是不可取的),在一般情况下,该任务基本上会减少到停止问题,因为您不知道该函数会在给定的特定参数下终止。理想情况下,异常应该阻止这种情况,但当然并非总是如此。

Now, perhaps the most reliable method is to analyse the source code (or more realistically CIL code) itself to see which exceptions might be thrown. This I believe may actually be workable. A simple algorithm might go something like:

现在,也许最可靠的方法是分析源代码(或更实际的 CIL 代码)本身以查看可能抛出哪些异常。我相信这实际上可能是可行的。一个简单的算法可能是这样的:

  1. Do a depth-first or breadth-first search of the given method. Find all methods/properties that are called anywhere within the method body, and recurse on them.
  2. For each block of CIL code in tree of methods/propreties, examine code for any exceptions that might be thrown, and add to a list.
  1. 对给定方法进行深度优先或广度优先搜索。查找在方法主体内任何地方调用的所有方法/属性,并对其进行递归。
  2. 对于方法/属性树中的每个 CIL 代码块,检查可能抛出的任何异常的代码,并将其添加到列表中。

This would even allow you to get detailed information about the exceptions, such as whether they are thrown directly by the called method, or deeper in the call stack, or even the exception messages themselves. Anyway, I'll consider giving an implementation of this a try later this afternoon, so I'll let you know how feasible the idea is then.

这甚至可以让您获得有关异常的详细信息,例如它们是由被调用的方法直接抛出,还是在调用堆栈的更深处,甚至异常消息本身。不管怎样,我会考虑在今天下午晚些时候尝试一下这个实现,所以我会告诉你这个想法的可行性。

回答by Mark

This answer was posted in the other question you reference and I know I have recommended it before in another similar question. You should give Exception Huntera try. It lists out every single exception that can possibly be thrown. When I ran it for the first time on my code I was quite surprised by the size of this list for even simple functions. There is a 30 day trial for free so there is no reason not to give it a try.

这个答案发布在您参考的另一个问题中,我知道我之前在另一个类似的问题中推荐过它。你应该试试Exception Hunter。它列出了可能抛出的每个异常。当我第一次在我的代码上运行它时,我对这个列表的大小感到非常惊讶,即使是简单的函数。有 30 天免费试用,因此没有理由不尝试一下。

回答by Rinat Abdullin

This should not be extremely hard. You can get list of exceptions created by a method like this:

这不应该是非常困难的。您可以获得由这样的方法创建的异常列表:

IEnumerable<TypeReference> GetCreatedExceptions(MethodDefinition method)
{
    return method.GetInstructions()
        .Where(i => i.OpCode == OpCodes.Newobj)
        .Select(i => ((MemberReference) i.Operand).DeclaringType)
        .Where(tr => tr.Name.EndsWith("Exception"))
        .Distinct();
}

Snippets use Lokad.Quality.dll from the Open Source Lokad Shared Libraries(which uses Mono.Cecil to do the heavy-lifting around code reflection). I actually put this code into one of the test cases in trunk.

片段使用来自开源Lokad 共享库的Lokad.Quality.dll(它使用 Mono.Cecil 围绕代码反射进行繁重的工作)。我实际上将这段代码放入了 trunk 中的测试用例之一

Say, we have a class like this:

比如说,我们有一个这样的类:

class ExceptionClass
{
    public void Run()
    {
        InnerCall();
        throw new NotSupportedException();
    }

    void InnerCall()
    {
        throw new NotImplementedException();
    }
}

then in order to get all exceptions from just the Run method:

然后为了从 Run 方法中获取所有异常:

var codebase = new Codebase("Lokad.Quality.Test.dll");
var type = codebase.Find<ExceptionClass>();
var method = type.GetMethods().First(md => md.Name == "Run");

var exceptions = GetCreatedExceptions(method)
    .ToArray();

Assert.AreEqual(1, exceptions.Length);
Assert.AreEqual("NotSupportedException", exceptions[0].Name);

Now all that remains is to walk down the method call stack down to a certain depth. You can get a list of methods referenced by a method like this:

现在剩下的就是沿着方法调用堆栈向下走到一定深度。您可以获取由这样的方法引用的方法列表:

var references = method.GetReferencedMethods();

Now before being able to call GetCreatedExceptionsupon any method down the stack we just need to actually look up into the codebase and resolve all MethodReference instances to MethodDefinition instances actually containing the byte code (with some caching to avoid scanning existing branches). That's the most time-consuming part of the code (since Codebase object does not implement any method lookups on top of Cecil), but that should be doable.

现在,在能够对堆栈中的任何方法调用GetCreatedExceptions之前,我们只需要实际查找代码库并将所有 MethodReference 实例解析为实际包含字节码的 MethodDefinition 实例(使用一些缓存以避免扫描现有分支)。这是代码中最耗时的部分(因为 Codebase 对象没有在 Cecil 之上实现任何方法查找),但这应该是可行的。

回答by JasonBock

I wrote an add-in for Reflector called ExceptionFinder that handles this. You can get it at:

我为 Reflector 编写了一个名为 ExceptionFinder 的插件来处理这个问题。你可以在:

http://exfinderreflector.codeplex.com/

http://exfinderreflector.codeplex.com/

Regards, Jason

问候, 杰森

回答by Steve Sheldon

This isn't so much an answer as building on top of the great work done by @Noldorin above. I used the code above and figured it would be really useful to have a tool that a developer could point at an arbitrary assembly/dll and see the list of exceptions thrown.

这与其说是建立在@Noldorin 上面所做的伟大工作之上,不如说是一个答案。我使用了上面的代码,并认为拥有一个开发人员可以指向任意程序集/dll 并查看抛出的异常列表的工具会非常有用。

By building on top of the work above, I built a tool which does exactly that. I shared the source on GitHub for anybody interested. It's super simple I only had a couple of hours free to write it but feel free to fork and make updates if you see fit...

通过在上述工作的基础上构建,我构建了一个完全可以做到这一点的工具。我在 GitHub 上为任何感兴趣的人分享了源代码。这非常简单,我只有几个小时的空闲时间来编写它,但是如果您认为合适,可以随意分叉并进行更新......

Exception Reflector on Github

Github 上的异常反射器