Parsing C#, finding methods and putting try/catch

2020-03-07 11:13发布

问题:

I know it sounds weird but I am required to put a wrapping try catch block to every method to catch all exceptions. We have thousands of methods and I need to do it in an automated way. What do you suggest?

I am planning to parse all cs files and detect methods and insert a try catch block with an application. Can you suggest me any parser that I can easily use? or anything that will help me...

every method has its unique number like 5006

public static LogEntry Authenticate(....)
        {
            LogEntry logEntry = null;
            try
            {
                ....
                return logEntry;
            }

            catch (CompanyException)
            {
                throw;
            }

            catch (Exception ex)
            {
                logEntry = new LogEntry(
                    "5006",
                    RC.GetString("5006"), EventLogEntryType.Error,
                    LogEntryCategory.Foo);

                throw new CompanyException(logEntry, ex);
            }
        }

I created this for this; http://thinkoutofthenet.com/index.php/2009/01/12/batch-code-method-manipulation/

回答1:

I had to do something kinda sorta similar (add something to a lot of lines of code); I used regex.

I would create a regex script that found the beginning of each function and insert the try catch block right after the beginning. I would then create another regex script to find the end of the function (by finding the beginning of the function right after it) and insert the try catch block at the end. This won't get you 100% of the way there, but it will hit maybe 80% which would hopefully be close enough that you could do the edge cases without too much more work.



回答2:

DONT DO IT. There is no good reason for pokemon ("gotta catch em all")error handling.

EDIT: After a few years, a slight revision is in order. I would say instead "at least dont do this manually". Use an AOP tool or weaver like PostSharp or Fody to apply this to the end result code, but make sure to consider other useful tracing or diagnostic data points like capturing time of execution, input parameters, output parameters, etc.



回答3:

Well if you have to do it, then you must. However, you might try to talk whoever is forcing you to do it into letting you use the UnhandledException event of the AppDomain class. It will give you a notification about every uncaught exception in any method before it is reported to the user. Since you can also get a stack trace from the exception object, you'll be able to tell exactly where every exception occurs. It is a much better solution than rigging your code with exception handlers everywhere.

With that said, if I had to do it, I'd use some regular expressions to identify the begin and end of each method and use that to insert some exception handler everywhere. The trick to writing a regular expression for this case will be Balancing Group Definition explained more in the MSDN documentation here. There is also a relevant example of using balancing groups here.



回答4:

Maybe whoever came up with the requirement doesn't understand that you can still catch all exceptions (at the top) without putting a try-catch in every single function. You can see an example of how to catch all unhandled exceptions here. I think this is a much better solution as you can actually do something with the exception, and report it, rather than blindly burying all exceptions, resulting in extremely hard to track down bugs.

This is similar to Scott's solution, but also adds an event handler to the Application.ThreadException exception which can happen if you are using threads. Probably best to use both in order to catch all exceptions.



回答5:

See my answer here which describes some of the performance trade offs you will be forced to live with if you use "gotta catch em all" exception handling.

As scott said the best way to do pretty much the same thing is the UnhandledException event. I think jeff actually discussed this very problem in an early SO podcast.



回答6:

I'm with StingyJack, don't do it!

However if the Gods on high decree that must be done, then see my answer to this question Get a method’s contents from a cs file



回答7:

First of all, I'm with StingyJack and Binary Worrier. There's a good reason exceptions aren't caught by default. If you really want to catch exceptions and die slightly nicer, you can put a try-catch block around the Application.Run() call and work from there.

When dealing with outside sources, (files, the Internet, etc), one should (usually) catch certain exceptions (bad connection, missing file, blah blah). In my book, however, an exception anywhere else means either 1) a bug, 2) flawed logic, or 3) poor data validation...

In summary, and to completely not answer your question, are you sure you want to do this?



回答8:

I wanted to write this as an answer to all answers then you can be aware via question RSS; requirement comes from our technical leader: here is his reason: we need no find out which func has a problem in production code, any problem has been reported as an alert, we put a unique code to ecery catch block and we see where the problem is. He knows there is a global error handling but it does not help in this scenario, and stacktrace is not accure in release mode, so he requires a try catch block like this:

everyMethod(...){
    try
    {
    ..
    catch(Exception e)
    {
       RaiseAlert(e.Message.. blabla)
    }
}


回答9:

If you put a RaiseAlert call in every method, the error stacks you receive will be very confusing, if not inaccurate, assuming that you reuse methods. The logging method should really only need to be called in events or the top-most method(s). If someone is pushing the issue that exception handling needs to be in every method, they don't understand exception handling.

A couple years back we implemented a practice that exception handling had to be done in every event and one developer read it as "every method." When they were finished, we had weeks worth undoing to do because no exception reported was ever reproducible. I'm assuming they knew better, like you do, but never questioned the validity of their interpretation.

Implementing AppDomain.UnhandledException is a good backup but your only recourse in that method is to kill the application once you log the exception. You'd have to write a global exception handler to prevent this.



回答10:

so here is an example for those wondering; 5006 is unique to this method;

public static LogEntry Authenticate(....)
        {
            LogEntry logEntry = null;
            try
            {
                ....
                return logEntry;
            }

            catch (CompanyException)
            {
                throw;
            }

            catch (Exception ex)
            {
                logEntry = new LogEntry(
                    "5006",
                    RC.GetString("5006"), EventLogEntryType.Error,
                    LogEntryCategory.Foo);

                throw new CompanyException(logEntry, ex);
            }
        }


回答11:

I guess you could use Aspect Oriented programming, something I would like to my hands dirty with. For example http://www.postsharp.org/aopnet/overview

Although this sort of requirements are indeed evil.



回答12:

If you really have to do it, why go to the trouble of modifying the source code, when you can modify the compiled executable/library directly

Have a look at Cecil (see website), It's a library that allows you to modify the bytecode directly, using this approach, your entire problem could be solved in a couple of hundred lines of C# code.



回答13:

Since you are posting a question here, I am sure this is one of those things you just have to do. So instead of banging your head against an unyielding wall, why not do what Scott suggested and use the AppDomain event handler. You'll meet the requirements without spending hours of quality billable hours doing grunt work. I am sure once you tell your boss how much testing updating each and every file would entail, using the event handler will be a no-brainer!



回答14:

So you are not really looking to put the same exact try-catch block in each function, right? You are going to have to tailor each try-catch to each function. Wow! Seems like a long way to "simplify" debugging.

If a user reports an error in production, why can't you just fire up Visual Studio and reproduce the steps and debug?



回答15:

If you absolutely have to add the try/catch block to every method and scott's answer (AppDomain.UnhandledException) is not sufficient, you can also look into interceptors. I believe the Castle project has a fairly good implementation of method interceptors.



回答16:

If you really have to do it, an alternative to wrapping the exception each time would be to use Exception.Data to capture the additional information and then rethrow the original exception ...

catch (Exception ex)
{
  logEntry = new LogEntry("5006",
                    RC.GetString("5006"), EventLogEntryType.Error,
                    LogEntryCategory.Foo);
  ex.Data.Add("5006",logEntry);
  throw;
}

Now at the top level you can just dump the contents of ex.Data to get all the additional information you might want. This allows you to put file names, useragents, ... and all manner of other useful information in the .Data collection to help understand why an exception occurred.



回答17:

I did some research work that require parsing of C# code about 2 years ago and discover that the SharpDevelop project has source code that does this really well. If you extracted the SharpDevParser project (this was two years ago, not sure if the project name stays the same) from the source code base, you can then use the parser object like this:

CSharpBinding.Parser.TParser = new CSharpBinding.Parser.TParser();
SIP.ICompilationUnitBase unitBase = sharpParser.Parse(fileName);

this gives you the compUnit.Classes which you can iterate through each class and find method within it.



回答18:

I was helping a friend find a memory leak in a C# XNA Game he was writing. I suggested we try and examine how many times each method was getting invoked. In order to keep count, I wrote a python script that added 2 lines to update a Dictionary with the details.

Basically I wrote a python script to modify some 400~ methods with 2 required lines. This code may help someone do more things, like the odd thing the OP wanted.

The code uses the path configured on the 3rd line, and iterates recursively while processing .cs files. It does sub-directories as well.

When it find a cs file, it looks for method declarations, it tries to be as careful as possible. MAKE A BACKUP - I AM NOT RESPONSIBLE IF MY SCRIPT VIOLATES YOUR CODE!!!

import os, re

path="D:/Downloads/Dropbox/My Dropbox/EitamTool/NetworkSharedObjectModel"
files = []

def processDir(path, files):
    dirList=os.listdir(path)
    for fname in dirList:
        newPath = os.path.normpath(path + os.sep + fname)
        if os.path.isdir(newPath):
            processDir(newPath, files)
        else:
            if not newPath in files:
                files.append(newPath)
                newFile = handleFile(newPath)
            if newPath.endswith(".cs"):
                writeFile(newPath, newFile)

def writeFile(path, newFile):
    f = open(path, 'w')
    f.writelines(newFile)
    f.close()

def handleFile(path):
    out = []
    if path.endswith(".cs"):
        f = open(path, 'r')
        data = f.readlines()
        f.close()

        inMethod = False
        methodName = ""
        namespace = "NotFound"
        lookingForMethodDeclerationEnd = False
        for line in data:
            out.append(line)
            if lookingForMethodDeclerationEnd:
                strippedLine = line.strip()
                if strippedLine.find(")"):
                    lookingForMethodDeclerationEnd = False
            if line.find("namespace") > -1:
                namespace = line.split(" ")[1][0:-2]
            if not inMethod:
                strippedLine = line.strip()
                if isMethod(strippedLine):
                    inMethod = True
                    if strippedLine.find(")") == -1:
                        lookingForMethodDeclerationEnd = True
                    previousLine = line
            else:
                strippedLine = line.strip()
                if strippedLine == "{":
                    methodName = getMethodName(previousLine)
                    out.append('            if (!MethodAccess.MethodAccess.Counter.ContainsKey("' + namespace + '.' + methodName + '")) {MethodAccess.MethodAccess.Counter.Add("' + namespace + '.' + methodName + '", 0);}')
                    out.append("\n" + getCodeToInsert(namespace + "." + methodName))
                    inMethod = False
    return out

def getMethodName(line):
    line = line.strip()
    lines = line.split(" ")
    for littleLine in lines:
        index = littleLine.find("(")
        if index > -1:
            return littleLine[0:index]


def getCodeToInsert(methodName):
    retVal = "          MethodAccess.MethodAccess.Counter[\"" + methodName + "\"]++;\n"
    return retVal

def isMethod(line):
    if line.find("=") > -1 or line.find(";") > -1 or line.find(" class ") > -1:
        return False
    if not (line.find("(") > -1):
        return False
    if line.find("{ }") > -1:
        return False
    goOn = False
    if line.startswith("private "):
        line = line[8:]
        goOn = True
    if line.startswith("public "):
        line = line[7:]
        goOn = True
    if goOn:
        return True
    return False

processDir(path, files)


标签: c# parsing