2016-12-28 3 views
0

Ich habe folgende Beispielklassen:Transforming Kette fließend Ausdrücke mit Roslyn

// Sample fluent style class 
namespace Fluent 
{ 
    public class FluentSample 
    { 
     private readonly IList<string> _options; 

     public FluentSample() 
     { 
      _options = new List<string>(); 
     } 

     public static FluentSample Build() 
     { 
      return new FluentSample(); 
     } 

     public FluentSample WithOption(string option) 
     { 
      _options.Add(option); 
      return this; 
     } 
    } 
} 

// Sample class that uses the one above 
public class FooSample 
{ 
    public void Build() 
    { 
     FluentSample.Build() 
      .WithOption("uppercase") 
      .WithOption("trim") 
      .WithOption("concat"); 
    } 
} 

Nehmen wir an, ich den Code zu transformieren möchten, dass die FluentSample-Klasse verwendet und ersetzen FluentSample.Build() mit Fluent.FluentSample.Build().WithOption("addnewline"). Um das zu tun, muss ich sicherstellen, dass es wirklich diese Klasse aufruft (und nicht eine andere mit dem gleichen Namen), was eine gewisse Symbolbindung erfordert.

Aus der Syntax Visualizer Fenster habe ich, dass es im Grunde ein InvocationExpressionSyntax Knoten gefunden, so dass ich zwingende VisitInvocationExpression von CSharpSyntaxRewriter:

public class Rewritter : CSharpSyntaxRewriter 
{ 
    private readonly SemanticModel SemanticModel; 

    public Rewritter(SemanticModel semanticModel) 
    { 
     SemanticModel = semanticModel; 
    } 

    public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) 
    { 
     var symbol = SemanticModel.GetSymbolInfo(node).Symbol; 
    } 
} 

die Symbole erhalte ich jedoch die vielen WithOption Anrufe . Die Syntaxanalyse zeigt, dass es eine Kette von Ausdrücken enthält, die nur zu mehr und mehr WithOption s führt. Wie kann ich überprüfen, ob dies wirklich ein Aufruf an Fluent.FluentSample ist und die Transformation anwenden?

Der ursprüngliche Code ist fast genau so, so dass die Aussagen immer in fließendem Stil sein werden.

+1

Wo genau ist „am Ende des Aufrufs“? Denken Sie daran, dass Leute 'var x = new FluentSample() schreiben können; x.WithOption ("a"); (x.WithOption ("b")). MitOption (c); '. Es wäre viel einfacher, wenn Sie sagen würden: "Wie kann ich überprüfen, ob es sich wirklich um einen Aufruf von' Fluent.FluentSample.Build() 'handelt und ersetzen Sie es durch' Fluent.FluentSample.Build(). MitOption ("addnewline") ' –

+0

Sie haben Recht, ich habe nicht darüber nachgedacht, es so zu formulieren. Außerdem wird der Code immer in fließendem Stil sein, keine Notwendigkeit, Randfälle abzudecken. Ich habe die Frage bearbeitet, um sie weiter zu klären. –

Antwort

1

Dies sollte Ihnen den Einstieg:

public class Rewritter : CSharpSyntaxRewriter 
{ 
    private readonly SemanticModel SemanticModel; 

    public Rewritter(SemanticModel semanticModel) 
    { 
     SemanticModel = semanticModel; 
    } 

    public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) 
    { 
     var symbol = SemanticModel.GetSymbolInfo(node).Symbol as IMethodSymbol; 

     // symbol could be null, e.g. when invoking a delegate 
     if (symbol == null) 
     { 
      return base.VisitInvocationExpression(node); 
     } 

     // symbol must be called Build and have 0 parameters 
     if (symbol.Name != "Build" || 
      symbol.Parameters.Length != 0) 
     { 
      return base.VisitInvocationExpression(node); 
     } 

     // TODO you might want to check that the parent is not an invocation of .WithOption("addnewline") already 

     // symbol must be a method on the type "Fluent.FluentSample" 
     var type = symbol.ContainingType; 

     if (type.Name != "FluentSample" || type.ContainingSymbol.Name != "Fluent") 
     { 
      return base.VisitInvocationExpression(node); 
     } 

     // TODO you may want to add a check that the containing symbol is a namespace, and that its containing namespace is the global namespace 

     // we have the right one, so return the syntax we want 
     return 
      SyntaxFactory.InvocationExpression(
       SyntaxFactory.MemberAccessExpression(
        SyntaxKind.SimpleMemberAccessExpression, 
        node, 
        SyntaxFactory.IdentifierName("WithOption")), 
       SyntaxFactory.ArgumentList(
        SyntaxFactory.SingletonSeparatedList(
         SyntaxFactory.Argument(
          SyntaxFactory.LiteralExpression(
           SyntaxKind.StringLiteralExpression, 
           SyntaxFactory.Literal("addnewline")))))); 

    } 
} 
+1

Das funktioniert genau so, wie ich es möchte: Nur eine Korrektur: Die Syntax für den Literalausdruck ist 'StringLiteralExpression' anstelle von' StringLiteralToken'. –

+0

@kopranb In der Tat, Tippfehler behoben. –