我是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
和__
的用法?
對我來說,看起來有點冒失/低效,但我會接受這個答案,直到我得到一個更好的答案。 –