【问题标题】:Is Roslyn the right tool for a compile-time Expression checking?Roslyn 是编译时表达式检查的正确工具吗?
【发布时间】:2012-03-20 06:47:35
【问题描述】:

我有一个工具包,其中包含许多方法,通常以Expression<Func<T,TProperty>> 作为参数。有些只能是单级 (o=>o.Name),而有些可以是多级 (o=>o.EmployeeData.Address.Street)。

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

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

然后我想到了罗斯林。 可以用 Roslyn 写出这样的东西吗?

【问题讨论】:

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

标签: c# .net roslyn


【解决方案1】:

是的,我认为 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。这是因为表达式树的语义在the currently unimplemented features 之间。

【讨论】:

  • Roslyn 推出了新的 CTP。我想现在是再试一次的好时机吗? :)
【解决方案2】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 2010-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-14
    • 1970-01-01
    • 2023-03-17
    相关资源
    最近更新 更多