11

I have a toolkit that has many methods often taking Expression<Func<T,TProperty>> as parameters. Some can be single-level only (o=>o.Name), while some can be multi-level (o=>o.EmployeeData.Address.Street).

I want to develop something (MSBuild Task? Visual Studio Plugin? hopefully the first) that reads all the user's .cs files, and gives build errors if the given parameter is not a property-expression (but something like o=>o.Contains("foo")), or if a multi-level expression is given where only a single-level is allowed.

I tried looking at compiled IL code first but since the expression trees are a C# compiler "trick", in IL all I see is creating expression instances and such, and while I could check each if only MemberExpressions (and the correct number of them) are created, it is not so great.

Then Roslyn came to my mind. Is it possible to write something like this with Roslyn?

4

2 回答 2

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
}

用法是这样的:

[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。这是因为表达式树的语义是当前未实现的特性之一。
于 2012-03-19T22:47:35.007 回答
5

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

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

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

我还应该提到,解析器对于 Roslyn 来说是完整的,所以你可以在没有语义信息的情况下做的越多越好。

于 2012-03-19T21:58:42.983 回答