如何用 SimpleLambdaExpression 中的另一个变量替换 lambda 参数的用法?
How do I replace usages of a lambda parameter with another variable in a SimpleLambdaExpression?
我是罗斯林的新手。我正在尝试编写一个分析器来检测 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("__"));
我正在尝试将 SimpleLambdaExpression
的 Body
替换为它的 Parameter
,但没有任何作用。我怀疑这行不通,但我还能如何在 lambda 中将 x
的用法替换为 __
?
您正在尝试替换 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);
我是罗斯林的新手。我正在尝试编写一个分析器来检测 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("__"));
我正在尝试将 SimpleLambdaExpression
的 Body
替换为它的 Parameter
,但没有任何作用。我怀疑这行不通,但我还能如何在 lambda 中将 x
的用法替换为 __
?
您正在尝试替换 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);