2017-02-27 35 views
2

我是Roslyn的新手。我試圖編寫一個分析器來檢測Select何時在foreach循環中迭代,例如,如何用SimpleLambdaExpression中的另一個變量替換lambda參數的用法?

foreach (TResult item in source.Select(x => x.Foo())) 
{ 
    ... 
} 

,我正在寫這樣的轉變陳述

foreach (TSource __ in source) 
{ 
    TResult item = __.Foo(); 
    ... 
} 

這裏代碼補丁提供者是我到目前爲止的代碼修復提供商的代碼。 (這有點長;在InlineSimpleLambdaExpressionAsync是其中的變化肉是的,但我包括一切從RegisterCodeFixesAsync爲背景。)

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 
{ 
    var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 

    var diagnostic = context.Diagnostics.First(); 
    var diagnosticSpan = diagnostic.Location.SourceSpan; 

    var selectInvocation = (InvocationExpressionSyntax)syntaxRoot.FindNode(diagnosticSpan); 
    var forEach = (ForEachStatementSyntax)selectInvocation.Parent; 

    context.RegisterCodeFix(
     CodeAction.Create(
      title: Title, 
      createChangedDocument: ct => InlineSelectorAsync(context.Document, forEach, selectInvocation, ct), 
      equivalenceKey: Title), 
     diagnostic); 
} 

private static Task<Document> InlineSelectorAsync(
    Document document, 
    ForEachStatementSyntax forEach, 
    InvocationExpressionSyntax selectInvocation, 
    CancellationToken ct) 
{ 
    var selectorExpr = selectInvocation.ArgumentList.Arguments.Single().Expression; 

    switch (selectorExpr.Kind()) 
    { 
     case SyntaxKind.SimpleLambdaExpression: 
      // This will be the most common case. 
      return InlineSimpleLambdaExpressionAsync(
       document, 
       forEach, 
       selectInvocation, 
       (SimpleLambdaExpressionSyntax)selectorExpr, 
       ct); 
    } 

    return Task.FromResult(document); 
} 

private static async Task<Document> InlineSimpleLambdaExpressionAsync(
    Document document, 
    ForEachStatementSyntax forEach, 
    InvocationExpressionSyntax selectInvocation, 
    SimpleLambdaExpressionSyntax selectorExpr, 
    CancellationToken ct) 
{ 
    var smodel = await document.GetSemanticModelAsync(ct).ConfigureAwait(false); 

    // First, change the foreach to iterate directly through the source enumerable, 
    // and remove the Select() method call. 
    // NOTE: GetSimpleMemberAccessExpression() is an extension method I wrote. 
    var sourceExpr = selectInvocation.GetSimpleMemberAccessExpression()?.Expression; 
    if (sourceExpr == null) 
    { 
     return document; 
    } 

    // Figure out the element type of the source enumerable. 
    var sourceTypeSymbol = smodel.GetTypeInfo(sourceExpr, ct).Type; 
    Debug.Assert(sourceTypeSymbol != null); 
    // NOTE: GetElementType is an extension method I wrote. 
    var elementTypeSymbol = sourceTypeSymbol.GetElementType(smodel); 

    // Now, update the foreach. Replace the element type of the selected enumerable 
    // with the element type of the source. Make '__' the identifier (TODO: Improve on this). 
    var ident = SyntaxFactory.Identifier("__"); 
    int position = forEach.Type.SpanStart; 
    var elementTypeSyntax = SyntaxFactory.IdentifierName(elementTypeSymbol.ToMinimalDisplayString(smodel, position)); 

    var newForEach = forEach 
     .WithType(elementTypeSyntax) 
     .WithIdentifier(ident) 
     .WithExpression(sourceExpr); 

    // Now, we have to take the selector and inline it. 
    var selectorBody = selectorExpr.Body as ExpressionSyntax; 
    Debug.Assert(selectorBody != null); 
    var selectorParam = selectorExpr.Parameter; 
    selectorBody = selectorBody.ReplaceNode(selectorParam, SyntaxFactory.IdentifierName("__")); // This doesn't work. 
    var selectorStatement = SyntaxFactory.LocalDeclarationStatement(
     SyntaxFactory.VariableDeclaration(
      type: forEach.Type, 
      variables: SyntaxFactory.SingletonSeparatedList(
       SyntaxFactory.VariableDeclarator(
        identifier: forEach.Identifier, 
        argumentList: null, 
        initializer: SyntaxFactory.EqualsValueClause(selectorBody))))); 

    var forEachStatment = forEach.Statement as BlockSyntax; 
    // TODO: Consider supporting non-block statements? Would that happen with no braces? 
    if (forEachStatment == null) 
    { 
     return document; 
    } 
    newForEach = newForEach.WithStatement(
     // NOTE: InsertStatements is an extension method I wrote. 
     forEachStatment.InsertStatements(0, selectorStatement)); 

    // Update the syntax root and the document. 
    var syntaxRoot = await document.GetSyntaxRootAsync(ct).ConfigureAwait(false); 
    syntaxRoot = syntaxRoot.ReplaceNode(forEach, newForEach); 
    return document.WithSyntaxRoot(syntaxRoot); 
} 

當我運行下面的代碼的代碼修復:

foreach (var item in array.Select(x => x.ToString())) 
{ 

} 

我得到:

  foreach (int __ in array) 
      { 
var item = x.ToString(); 
      } 

哪個(除了空格),幾乎正是我想要的,只是我無法弄清楚如何替換參數x__。特別是,該行似乎並不奏效:

selectorBody = selectorBody.ReplaceNode(selectorParam, SyntaxFactory.IdentifierName("__")); 

我試圖與它Parameter更換的Body,它什麼都不做。我懷疑這是行不通的,但我怎樣才能在lambda中替換x__的用法?

回答

1

您嘗試替換已在selectorBody缺少selectorParam節點,因爲在你的情況selectorBody是方法(x.ToString() - InvocationExpressionSyntax)的呼叫。您可以通過重寫代碼得到更換如下:

var selectorBody = selectorExpr.Body as InvocationExpressionSyntax; 
var nodeToReplace = (selectorBody.Expression as MemberAccessExpressionSyntax).Expression; 
selectorBody = selectorBody.ReplaceNode(nodeToReplace, SyntaxFactory.IdentifierName("__")); 

或者,如果你想要依靠的參數,那麼你應該通過以下方式代替不SyntaxNode,但SyntaxTokens

var selectorBody = selectorExpr.Body as ExpressionSyntax; 
var selectorParam = selectorExpr.Parameter.Identifier; 
IEnumerable<SyntaxToken> tokensToReplace = selectorBody.DescendantTokens() 
                 .Where(token => String.Equals(token.Text, selectorParam.Text)); 
selectorBody = selectorBody.ReplaceTokens(tokensToReplace, (t1, t2) => SyntaxFactory.IdentifierName("__").Identifier); 
+0

對我來說,看起來有點冒失/低效,但我會接受這個答案,直到我得到一個更好的答案。 –

相關問題