如何在SimpleLambdaExpression中用另一个变量替换lambda参数的用法?

时间:2017-02-27 02:12:46

标签: c# .net roslyn

我是Roslyn的新手。我试图编写一个分析器来检测 private void DoPCommand(object parameter) { DataGrid dg = parameter as DataGrid; if (dg != null) foreach (var r in dg.Items) { DataGridRow row = dg.ItemContainerGenerator.ContainerFromItem(r) as DataGridRow; if (row != null) row.BindingGroup.UpdateSources(); } } 循环中Select的迭代时间,例如。

foreach

我正在编写一个代码修复提供程序,将这些语句转换为

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("__")); 的{​​{1}}替换为BodySimpleLambdaExpression无效。我怀疑这不会起作用,但我怎样才能用Parameter替换x lambda中的用法?

1 个答案:

答案 0 :(得分:1)

您正在尝试替换selectorParam中缺少的selectorBody节点,因为在您的情况下,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);