问题 Roslyn是编译时表达式检查的正确工具吗?


我有一个工具包,经常采用许多方法 Expression<Func<T,TProperty>> 作为参数。有些可能只是单级(o=>o.Name),有些可以是多层次的(o=>o.EmployeeData.Address.Street)。

我想开发一些东西(MSBuild Task?Visual Studio Plugin?希望是第一个),它读取所有用户的.cs文件,如果给定的参数不是属性表达式,则会产生构建错误(但是类似于 o=>o.Contains("foo")),或者如果给出一个只允许单级的多级表达式。

我首先尝试查看已编译的IL代码,但由于表达式树是C#编译器“技巧”,所以我在IL中看到的是创建表达式实例等等,而我 可以 检查每个是否只创建了MemberExpressions(及其正确的数量),它不是那么好。

然后罗斯林浮现在我的脑海里。 是不是可以用Roslyn写这样的东西?


6407
2018-03-19 10:05


起源

为什么需要强制执行这些约束? - Daniel Hilgarth
因为我在这些方法中做的事情(属性更改处理,错误检查等)只对属性表达式有意义 - TDaver
因为这感觉像一件有趣的事:) - TDaver
这应该很容易作为Roslyn VS插件。你甚至会得到漂亮的波浪线。 - CodesInChaos
@CodeInChaos:你能指出我应该在Roslyn寻找什么课程吗?我之前从未使用过Roslyn ...此外,它可以在MSBuild任务中完成吗?我觉得那些插件更容易使用... - TDaver


答案:


是的,我认为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
}

用法如下:

[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

上面的代码有几个问题:

  1. 它完全没有优化。
  2. 它可能需要更多的错误检查。
  3. 这是行不通的。 至少在目前的CTP中。表达方式 semanticModel.GetSemanticInfo(memberAccess, cancellationToken).Symbol 接近尾声总是回来 null。这是因为表达式树的语义是其中之一 目前尚未实现的功能

11
2018-03-19 22:47



你刚写完我的作业吗? :d - TDaver
罗斯林推出了新的CTP。我想这是再次尝试这个的好时机吗? :) - TDaver


是的,这完全有可能。问题是Roslyn还没有支持所有语言结构,所以你可能遇到一些不支持的东西。表达式树不受支持,因为Roslyn无法编译生成表达式的代码,但是你应该能够使得一些东西能够运行得足够远。

在较高的层次上,如果您想将其实现为MSBuild任务,则可以在构建任务中调用 Roslyn.Services.Workspace.LoadSolution 要么 Roslyn.Services.Workspace.LoadStandaloneProject。然后,您将遍历语法树,寻找各种方法的提及,然后绑定它们以确保它实际上是您认为正在调用的方法。从那里,您可以找到lambda语法节点,并从那里执行您想要的任何语法/语义分析。

CTP中有一些您可能会觉得有用的示例项目,例如 RFxCopConsoleCS project,在Roslyn中实现一个简单的FxCop样式规则。

我还应该提一下,Roslyn的解析器是完整的,所以没有语义信息就能做得越多越好。


5
2018-03-19 21:58