我编写了一个分析器来强制库使用GetAwaiter()。GetResult()而不是使用.Result / .Wait()来阻塞线程
几个小时后,我试图找到一种方法来生成一个涵盖我所有测试用例的syntaxtree,我试图使用ReplaceToken方法。
public static TRoot ReplaceToken<TRoot>(this TRoot root, SyntaxToken tokenInList, IEnumerable<SyntaxToken> newTokens) where TRoot : SyntaxNode
{
return (TRoot) root.ReplaceTokenInListCore(tokenInList, newTokens);
}
似乎是完美的方法 - 但是我从来没有使用这种方法。
我最终这样做了(对我来说,这看起来很懒):
private async Task<Document> ReplaceWithGetAwaiterGetResultAsync(Document document, IdentifierNameSyntax declaration, CancellationToken cancellationToken)
{
var source = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
if(declaration.Identifier.ValueText == "Result")
return document.WithText(source.Replace(declaration.Span, "GetAwaiter().GetResult()"));
if(declaration.Identifier.ValueText == "Wait")
return document.WithText(source.Replace(new TextSpan(declaration.SpanStart, 6), "GetAwaiter().GetResult()"));
return document;
}
有没有人知道如何改变像
这样的更好方法Task.Run(() => 42).Result
进入
Task.Run(() => 42).GetAwaiter.GetResult()
E.g。这个版本:
var tokens = SyntaxFactory.ParseTokens("GetAwaiter().GetResult()");
var root = await document.GetSyntaxRootAsync(cancellationToken);
var replaced = root.ReplaceToken(declaration.Identifier, tokens);
return document.WithSyntaxRoot(replaced.WithAdditionalAnnotations());
不会崩溃。
答案 0 :(得分:2)
实施解决方案时遇到的问题是您尝试更换令牌。但是,结果属性的访问权限不是令牌,而是SyntaxNode。
代码的结构由一个包含MethodInvocationExpression(Task.Run())的MemberAccessExpression(Task.Run()。Result)组成。
您要实现的是删除MemberAccessExpression,创建一个新的MethodInvocationExpression(调用GetAwaiter())并将原始InvocationExpression插入到新的Expression中,以便在原始表达式上调用GetAwaiter()。
然后你需要创建另一个 Incovation Expression来调用GetResult()。
为此,您需要创建一个这样的新节点,并使用SyntaxRoot替换旧节点:
private async Task<Document> ReplaceWithAwaiter(Document document,
IdentifierNameSyntax nameSyntax, CancellationToken cancellationToken)
{
var memberAccess = nameSyntax.Ancestors().OfType<MemberAccessExpressionSyntax>()
.First();
// Create .GetAwaiter()
var invocationOfAwaiter = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
memberAccess.Expression, SyntaxFactory.IdentifierName("GetAwaiter")));
// Create .GetResult()
var invocationOfGetResult = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
invocationOfAwaiter, SyntaxFactory.IdentifierName("GetResult")));
// Replace .Result by .GetAwaiter().GetResult()
var syntaxRoot = await document.GetSyntaxRootAsync();
syntaxRoot = syntaxRoot.ReplaceNode(memberAccess, invocationOfGetResult);
return document.WithSyntaxRoot(syntaxRoot);
}
对于.Wait(),代码有点不同,但同样的原则适用。在这种情况下,您希望替换InvocationExpression而不是MemberAccessExpression。
private async Task<Document> ReplaceWithAwaiterForWait(Document document,
IdentifierNameSyntax nameSyntax, CancellationToken cancellationToken)
{
// Get the Invocation Task.Run().Wait()
var invocationOfWait = nameSyntax.Ancestors().OfType<InvocationExpressionSyntax>()
.First();
// Get the Access for Task.Run().Wait
var memberAccessOfWait = (MemberAccessExpressionSyntax)invocationOfWait.Expression;
// Get the Invocation Task.Run()
var invocationOfTaskRun = memberAccessOfWait.Expression;
// Create new Expressions
var invocationOfAwaiter = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
invocationOfTaskRun, SyntaxFactory.IdentifierName("GetAwaiter")));
var invocationOfGetResult = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
invocationOfAwaiter, SyntaxFactory.IdentifierName("GetResult")));
var syntaxRoot = await document.GetSyntaxRootAsync();
// Replace the old expression
syntaxRoot = syntaxRoot.ReplaceNode(invocationOfWait, invocationOfGetResult);
return document.WithSyntaxRoot(syntaxRoot);
}
如果您不习惯使用单个节点的生成,您也可以使用SyntaxFactory.ParseExpression()生成一个新表达式并替换旧表达式:
private async Task<Document> ReplaceWithAwaiterWithParse(Document document,
IdentifierNameSyntax nameSyntax, CancellationToken cancellationToken)
{
var invocationOfWait = nameSyntax.Ancestors().OfType<InvocationExpressionSyntax>()
.First();
var memberAccessOfWait = (MemberAccessExpressionSyntax)invocationOfWait.Expression;
var invocationOfBoth = SyntaxFactory.ParseExpression(
memberAccessOfWait.Expression.ToFullString()
+ ".GetAwaiter().GetResult()");
var syntaxRoot = await document.GetSyntaxRootAsync();
syntaxRoot = syntaxRoot.ReplaceNode(invocationOfWait, invocationOfBoth);
return document.WithSyntaxRoot(syntaxRoot);
}
虽然这个解决方案更短更简洁,但您不会处理字符串而不是SyntaxTree,这可能会产生各种问题。此外,性能比使用SyntaxFactory创建单个对象要差一些。但是,您不必自己创建调用的每个部分。