我有一个工具包,有许多方法经常Expression<Func<T,TProperty>>作为参数.有些可以是单级(o=>o.Name),有些可以是多级(o=>o.EmployeeData.Address.Street).
我想开发一些东西(MSBuild Task?Visual Studio Plugin?希望是第一个)读取所有用户的.cs文件,如果给定的参数不是属性表达式(o=>o.Contains("foo")或类似的东西),或者如果是多个,则会产生构建错误给出了-level表达式,其中只允许单个级别.
我首先尝试查看已编译的IL代码,但由于表达式树是C#编译器"技巧",因此我在IL中看到的只是创建表达式实例等等,而我可以检查每个是否只有MemberExpressions(以及它们的正确数量)创造了,它不是那么伟大.
然后罗斯林浮现在我的脑海里.可以用Roslyn写这样的东西吗?
svi*_*ick 10
是的,我认为Roslyn及其代码问题正是这个问题的正确工具.使用它们,您可以在键入时分析代码并创建在Visual Studio中显示为其他错误的错误(或警告).
我试图创建这样的代码问题:
[ExportSyntaxNodeCodeIssueProvider("PropertyExpressionCodeIssue", LanguageNames.CSharp, typeof(InvocationExpressionSyntax))]
class PropertyExpressionCodeIssueProvider : ICodeIssueProvider
{
[ImportingConstructor]
public PropertyExpressionCodeIssueProvider()
{}
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken)
{
var invocation = (InvocationExpressionSyntax)node;
var semanticModel = document.GetSemanticModel(cancellationToken);
var semanticInfo = semanticModel.GetSemanticInfo(invocation, cancellationToken);
var methodSymbol = (MethodSymbol)semanticInfo.Symbol;
if (methodSymbol == null)
yield break;
var attributes = methodSymbol.GetAttributes();
if (!attributes.Any(a => a.AttributeClass.Name == "PropertyExpressionAttribute"))
yield break;
var arguments = invocation.ArgumentList.Arguments;
foreach (var argument in arguments)
{
var lambdaExpression = argument.Expression as SimpleLambdaExpressionSyntax;
if (lambdaExpression == null)
continue;
var parameter = lambdaExpression.Parameter;
var memberAccess = lambdaExpression.Body as MemberAccessExpressionSyntax;
if (memberAccess != null)
{
var objectIdentifierSyntax = memberAccess.Expression as IdentifierNameSyntax;
if (objectIdentifierSyntax != null
&& objectIdentifierSyntax.PlainName == parameter.Identifier.ValueText
&& semanticModel.GetSemanticInfo(memberAccess, cancellationToken).Symbol is PropertySymbol)
continue;
}
yield return
new CodeIssue(
CodeIssue.Severity.Error, argument.Span,
string.Format("Has to be simple property access of '{0}'", parameter.Identifier.ValueText));
}
}
#region Unimplemented ICodeIssueProvider members
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxToken token, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxTrivia trivia, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
#endregion
}
Run Code Online (Sandbox Code Playgroud)
用法如下:
[AttributeUsage(AttributeTargets.Method)]
class PropertyExpressionAttribute : Attribute
{ }
…
[PropertyExpression]
static void Foo<T>(Expression<Func<SomeType, T>> expr)
{ }
…
Foo(x => x.P); // OK
Foo(x => x.M()); // error
Foo(x => 42); // error
Run Code Online (Sandbox Code Playgroud)
上面的代码有几个问题:
semanticModel.GetSemanticInfo(memberAccess, cancellationToken).Symbol接近结尾的表达总是返回null.这是因为表达式树的语义是当前未实现的特征之一.是的,这完全有可能.问题是Roslyn还没有支持所有语言结构,所以你可能遇到一些不支持的东西.表达式树不受支持,因为Roslyn无法编译生成表达式的代码,但是您应该能够使得某些东西能够运行得足够远.
在较高的层次上,如果您想将其实现为MSBuild任务,则可以在构建任务中调用Roslyn.Services.Workspace.LoadSolution或Roslyn.Services.Workspace.LoadStandaloneProject.然后,您将遍历语法树,查找各种方法的提及,然后绑定它们以确保它实际上是您认为正在调用的方法.从那里,您可以找到lambda语法节点,并从那里执行您想要的任何语法/语义分析.
CTP中有一些您可能会觉得有用的示例项目,例如RFxCopConsoleCS项目,它在Roslyn中实现了一个简单的FxCop样式规则.
我还要提一下,解析器对于Roslyn来说是完整的,所以没有语义信息就能做得越多越好.