1

I'm changing our webservices to an async model. And for that I have to change over a hundred methods.

Doing it manually is one (unappealing) option. Is there no way to programmatically parse & change multiple functions/code files?

Example:

[Webmethod]
public void MyWebservice (string parameter1, string parameter2, string parameter3)
{
  //Logic here
}

And change this to:

public void InternalMyWebservice (string parameter1, string parameter2, string parameter3, AsyncCallback callback)
{
  //Logic here
}

[Webmethod]
public void BeginMyWebservice (string parameter1, string parameter2, string parameter3, AsyncCallback callback, object asyncState)
{
  //Queue InternalMyWebservice in a threadpool
}

public void EndMyWebservice(IAsyncResult asyncResult)
{
  //Set return values
}

It's basically the same thing I have to do for each webservice. Change the name to "InternalX", add a parameter and create the begin & end method.

Carra
  • 17,808
  • 7
  • 62
  • 75

6 Answers6

5

You should be able to use the CSharpCodeProvider.Parse method to generate a CodeCompileUnit instance which is a object oriented representation of your code. With that you can drill down to your methods, change the arguments, add new methods and stuff and when you're finished you can save the code back to a textfile. You generate code by calling CodeDomProvider.GenerateCodeFromCompileUnit passing the modified CodeCompileUnit

Provides access to instances of the C# code generator and code compiler.

Pauli Østerø
  • 6,878
  • 2
  • 31
  • 48
  • +1 for suggesting `CSharpCodeProvider.Parse`, but generating code using CodeDOM is a huge PITA – Niki Jan 05 '11 at 12:19
  • i don't find it THAT difficult and this is a fairly small task so it should be manageable – Pauli Østerø Jan 05 '11 at 12:31
  • I didn't say it was difficult, I said it was a pain in the ass ;-) Generating one line of C# code typically takes 3-10 lines of CodeDom code. If you use a template engine, you have to write one line: The line you want to get generated. – Niki Jan 05 '11 at 12:58
  • Nice to know. But it's a bit overkill for the 100 webservices I have to change. – Carra Jan 05 '11 at 16:14
  • This is not going to work, as CSharpCodeProvider.Parse will raise a "This CodeDomProvider does not support this method" - see https://stackoverflow.com/questions/26093271/parsing-c-sharp-source-code – RBA Mar 26 '19 at 10:49
2

Try to find a solution by Resharper and if not do some text replacing by Regex.

Jahan Zinedine
  • 14,616
  • 5
  • 46
  • 70
2

You could use a parser generator like ANTLR. Writing an ANTLR grammar for a subset of C# that only parses class and method declarations and simply ignores the method code shouldn't be to hard. Or you could use one of the C# grammars from the ANTLR site.

ANTLR has a feature called a "rewrite grammars" (e.g. look this question) that is very close to what you want to do.

But personally, I wouldn't put the generated methods in one file with the actual methods. If you find a bug in the generated code and want to re-generate them, the parser gets more complex because it has to recognize the methods it generated previously. And the temptation to edit the generated methods is very high. Also, it seems to violate the single-responsibility principle, but that might be a matter of taste.

I would put the generated methods in a separate file (a derived class or a partial class declaration). This has the advantage that you don't need a parser: If the non-generated class file compiles (maybe with abstract or partial method declarations in it), you can compile it and simply use well-known reflection mechanisms to get all the information you want. All you need is a templating framework like StringTemplate or T4 to generate the code.

Community
  • 1
  • 1
Niki
  • 15,662
  • 5
  • 48
  • 74
1

Why not just write one wrapper class, which will get the objects of existing classes (or delegates) at construction and call the needed methods asynchronously? The methods of the existing classes could still be synchronous.

Vlad
  • 35,022
  • 6
  • 77
  • 199
1

The below code does the replacement but is heavily dependent on the formatting of the input source file.

Assumptions

  • Webmethod begins on a new line with prefix 'public void'
  • Parameters are on the same line
  • Opening and closing brackets ({ and }) are on separate lines.


Code can be optimized and hard coding can be removed.


class CodeChanger
{
    private Dictionary webMethodDictionary;

    public CodeChanger()
    {
        webMethodDictionary = new Dictionary();
    }

    public void ChangeCode(string oldFilePath, string newFilePath)
    {
        StringBuilder newFileContents = new StringBuilder();
        StringBuilder webserviceMethodContents = new StringBuilder();
        Encoding iso88591Encoding = Encoding.GetEncoding("ISO-8859-1");
        string readLine;
        using (StreamReader streamReader = new StreamReader(oldFilePath, iso88591Encoding))
        {
            while ((readLine = streamReader.ReadLine()) != null)
            {
                if (!string.IsNullOrEmpty(readLine.Trim()))
                {
                    if (string.Equals(readLine, "[Webmethod]"))
                    {
                        // Read the next line - method signature
                        if ((readLine = streamReader.ReadLine()) != null)
                        {
                            readLine = readLine.Trim();
                            if (readLine.StartsWith("public void"))
                            {
                                string methodName = readLine.Split(new char[] { ' ' })[2];
                                Webmethod webMethod = new Webmethod(methodName);
                                webMethodDictionary.Add(methodName, webMethod);

                                // Process parameters
                                ProcessParameters(readLine, methodName, webMethod);

                                // Process Body
                                if ((readLine = streamReader.ReadLine()) != null)
                                {
                                    StringBuilder methodBody = new StringBuilder();
                                    readLine = readLine.Trim();
                                    if (string.Equals(readLine, "{"))
                                    {
                                        int bracketCounter = 1;
                                        while ((readLine = streamReader.ReadLine()) != null)
                                        {
                                            if (string.Equals(readLine.Trim(), "}"))
                                            {
                                                bracketCounter--;
                                            }
                                            else if (string.Equals(readLine.Trim(), "{"))
                                            {
                                                bracketCounter++;
                                            }

                                            if (bracketCounter != 0)
                                            {
                                                methodBody.AppendLine(readLine);
                                            }
                                            else
                                            {
                                                break;
                                            }
                                        }

                                        webMethod.AddBody(methodBody.ToString());
                                    }
                                }

                                newFileContents.AppendLine(GenerateNewWebmethods(webMethod));
                            }
                        }
                    }
                    else
                    {
                        newFileContents.AppendLine(readLine);
                    }
                }
                else
                {
                    newFileContents.AppendLine();
                }
            }
        }

        using (StreamWriter writer = new StreamWriter(newFilePath, false, iso88591Encoding))
        {
            writer.Write(newFileContents.ToString());
        }
    }

    private static void ProcessParameters(string readLine, string methodName, Webmethod webMethod)
    {
        int positionOpenBrackets = string.Concat("public void ", methodName, " ").Length;
        string parametersString = readLine.Substring(positionOpenBrackets).Trim();
        parametersString = parametersString.TrimStart(new char[] { '(' });
        parametersString = parametersString.TrimEnd(new char[] { ')' });

        string[] parameters = parametersString.Split(new char[] { ',' });
        foreach (string parameter in parameters)
        {
            string[] splitParameters = parameter.Trim().Split(new char[] { ' ' });
            webMethod.AddParameter(splitParameters[0].Trim(), splitParameters[1].Trim());
        }
    }

    private string GenerateNewWebmethods(Webmethod webmethod)
    {
        StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.AppendLine(GenerateInternal(webmethod));
        stringBuilder.AppendLine(GenerateBegin(webmethod));
        stringBuilder.Append(GenerateEnd(webmethod));

        return stringBuilder.ToString();
    }

    private string GenerateInternal(Webmethod webmethod)
    {
        StringBuilder stringBuilder = new StringBuilder();

        string parametersString = GenerateParameterString(webmethod);

        stringBuilder.AppendLine(string.Format("public void Internal{0} ({1}, AsyncCallback callback)",
            webmethod.Name, parametersString.Trim().TrimEnd(',')));
        stringBuilder.AppendLine("{");
        stringBuilder.Append(webmethod.Body);
        stringBuilder.AppendLine("}");

        return stringBuilder.ToString();
    }

    private string GenerateEnd(Webmethod webmethod)
    {
        StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.AppendLine(string.Format("public void End{0} (IAsyncResult asyncResult)", webmethod.Name));
        stringBuilder.AppendLine("{");
        stringBuilder.AppendLine("//Set return values");
        stringBuilder.Append("}");

        return stringBuilder.ToString();
    }

    private string GenerateBegin(Webmethod webmethod)
    {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.AppendLine("[Webmethod]");
        string parametersString = GenerateParameterString(webmethod);

        stringBuilder.AppendLine(string.Format("public void Begin{0} ({1}, AsyncCallback callback, object asyncState)",
            webmethod.Name, parametersString.Trim().TrimEnd(',')));
        stringBuilder.AppendLine("{");
        stringBuilder.AppendLine("//Queue InternalMyWebservice in a threadpool");
        stringBuilder.AppendLine("}");

        return stringBuilder.ToString();
    }

    private static string GenerateParameterString(Webmethod webmethod)
    {
        StringBuilder parametersStringBuilder = new StringBuilder();
        foreach (MethodParameter parameter in webmethod.Parameters)
        {
            string parameterString = string.Concat(parameter.Type, " ", parameter.Name, ", ");
            parametersStringBuilder.Append(parameterString);
        }

        return parametersStringBuilder.ToString();
    }
}

class Webmethod
{
    public IList Parameters { get; private set; }
    public string Name { get; private set; }
    public string Body { get; private set; }


    public Webmethod(string name)
    {
        Parameters = new List();
        Name = name;
    }

    public void AddParameter(string paramType, string paramName)
    {
        MethodParameter methodParameter = new MethodParameter
                                            {
                                                Type = paramType,
                                                Name = paramName
                                            };

        Parameters.Add(methodParameter);
    }

    public void AddBody(string body)
    {
        Body = body;
    }
}

class MethodParameter
{
    public string Type { get; set; }
    public string Name { get; set; }
}

Usage


CodeChanger cc = new CodeChanger();
cc.ChangeCode(@"D:\1.cs", @"D:\3.cs"); 

This can also be modified to suit the System.CodeDom approach.

Devendra D. Chavan
  • 8,871
  • 4
  • 31
  • 35
  • Note that this kind of parser is very fragile: What if the attribute's written `[Webmethod()]`? Or `[SomeAttribute, WebMethod]`? What if there's an xml comment between the attribute and the function? What if the return type contains spaces (e.g. `Dictionary`)? What if a comment in the function contains the characters '{' or '}'? What if there's more than just one bracket in the same line (e.g. `var data = new[] { 1,2,3 }`)? You can't parse C# this way. Write a clean parser or use a parser-generator to do it for you. – Niki Jan 05 '11 at 12:51
  • @nikie, you are right. The code will break at the slightest deviations from the assumptions. I did not know about the code generator (System.CodeDom) class, it provides a much better way to generate the class. This code can be used a starting point. – Devendra D. Chavan Jan 05 '11 at 12:56
  • Thanks a lot for the code. But I'll go with one of the standard parsers. Should be safer. – Carra Jan 05 '11 at 13:09
0

I am actually not too familiar with the .NET framework, but this could definitely be accomplished with Regular Expressions.

SUDO Los Angeles
  • 1,555
  • 12
  • 10
  • Just be sure not to cling to regex if the problem should become larger. They may work for this, but they don't do too well for such things in the general case. –  Jan 05 '11 at 10:51
  • I wouldn't do that: Function declaration syntax is quite complex. If you forget some edge case (e.g. comments between parameters, attributes on parameters, nested generic types) your regex will simply ignore a method, without warnings or errors - C# is not a regular language (not even this subset), use a parse if you want to parse it. – Niki Jan 05 '11 at 12:01
  • Got a problem? Use regular expressions. Now you've got two problems :) – Carra Jan 05 '11 at 13:09