如何使用Roslyn获取编译时间常量值

时间:2016-02-27 13:00:55

标签: c# roslyn roslyn-code-analysis

我有一些示例代码可以简化我想要完成的任务。

OptionAtrribute.cs

using System;
public class OptionAttribute : Attribute
{
    public OptionAttribute(string option)
    {
        this.PickedOption = option;
    }

    public string PickedOption { get; private set; }
}

Options.cs

using System;
public class Options
{
    public const string Cat = "CT";
    public const string Dog = "DG";
    public const string Monkey = "MNKY";
}

SomeClass.cs

using System;
[Option(Options.Dog)]
public class SomeClass
{

}

如何在“SomeClass”类中获取“OptionAttribute”并获取“PickedOption”值?

更新

我不是在问如何使用反射。这是用于在保存代码的文件时生成代码。我此时没有更新的dll因此反射不起作用我试图使用Roslyn来解析实际文件。 以下是我尝试的内容

string solutionPath = @"C:\Project\Project.sln";
var msWorkspace = MSBuildWorkspace.Create();

var solution = await msWorkspace.OpenSolutionAsync(solutionPath);
var project = solution.Projects.FirstOrDefault(p => p.Name == "Project1");
var compilation = await project.GetCompilationAsync();

var document = project.Documents.FirstOrDefault(d => d.Name == "Code.cs");

SyntaxTree syntaxTree = null;
document.TryGetSyntaxTree(out syntaxTree);
var semanticModel = compilation.GetSemanticModel(syntaxTree);

var commandCategoryAttribute = compilation.GetTypeByMetadataName("Project1.OptionAttribute");
var classDeclaration = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().Skip(2).FirstOrDefault();
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration);
var attrSymbol = classSymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass.MetadataName == commandCategoryAttribute.MetadataName);     
var attrSyntax = classDeclaration.AttributeLists.FirstOrDefault().Attributes.FirstOrDefault();

我的解决方案

我开始工作了!

public void Test()
{
    string solutionPath = @"C:\Project\Project.sln";
    var msWorkspace = MSBuildWorkspace.Create();
    var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
    var project = solution.Projects.FirstOrDefault(p => p.Name == "Project1");
    var compilation = project.GetCompilationAsync().Result;

    var document = project.Documents.FirstOrDefault(d => d.Name == "SomeClass.cs");
    SyntaxTree syntaxTree = null;
    document.TryGetSyntaxTree(out syntaxTree);
    SemanticModel semanticModel = compilation.GetSemanticModel(syntaxTree);


    ClassDeclarationSyntax classDeclaration = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
    var attr = classDeclaration.AttributeLists.SelectMany(a => a.Attributes).FirstOrDefault(a => a.Name.ToString() == "Option");


    var exp = attr.ArgumentList.Arguments.First().Expression;
    string value = null;
    var mem = exp as MemberAccessExpressionSyntax;
    if (mem != null)
    {
        value = ResolveMemberAccess(mem, solution, compilation)?.ToString();
    }
    else
    {
        var lit = exp as LiteralExpressionSyntax;
        if (lit != null)
        {
            value = semanticModel.GetConstantValue(lit).Value?.ToString();
        }
    }
}
public object ResolveMemberAccess(MemberAccessExpressionSyntax memberSyntax, Solution solution, Compilation compilation)
{
    var model = compilation.GetSemanticModel(memberSyntax.SyntaxTree);
    var memberSymbol = model.GetSymbolInfo(memberSyntax).Symbol;
    var refs = SymbolFinder.FindReferencesAsync(memberSymbol, solution).Result.FirstOrDefault();

    if (refs != null)
    {
        var defSyntax = refs.Definition.DeclaringSyntaxReferences.First();
        var parent = compilation.GetSemanticModel(defSyntax.SyntaxTree);
        var syn = defSyntax.GetSyntax();

        var literal = syn.DescendantNodes().OfType<LiteralExpressionSyntax>().FirstOrDefault();
        if (literal != null)
        {
            var val = parent.GetConstantValue(literal);
            return val.Value;
        }
        else
        {
            var memberAccess = syn.DescendantNodes().OfType<MemberAccessExpressionSyntax>().FirstOrDefault();
            if (memberAccess != null)
            {
                return ResolveMemberAccess(memberAccess, solution, compilation);
            }
        }
    }
    return null;
}

谢谢!

2 个答案:

答案 0 :(得分:2)

听起来好像你实际上非常接近。考虑将其添加到您的代码中:

attrSyntax.ArgumentList.Arguments.First().Expression.ToString()

这将整齐地返回

  

Options.Dog

所以你知道你有这方面的信息。如果你稍微改变它,例如:

var expression = attrSyntax.ArgumentList.Arguments.First().Expression as MemberAccessExpressionSyntax;
expression.Name.Identifier.ValueText.Dump();

您获得输出

  

但是,如果你想要它指向的实际值,你可以这样做:

var x = classDeclaration.AttributeLists.First().Attributes.First().ArgumentList.Arguments.First();
semanticModel.GetConstantValue(x.Expression).Value.Dump();

这将输出

  

DG

答案 1 :(得分:2)

SemanticModel.GetConstantValue()在VS2017上无法正常工作。

以下是处理简单字符串语句的示例:

    private string GetDllName(AttributeSyntax importAttribute, Compilation compilation)
    {
        var expression = importAttribute.ArgumentList.Arguments[0].Expression;
        return GetConstantValue(expression, compilation);
    }

    private string GetConstantValue(ExpressionSyntax expression, Compilation compilation)
    {
        if (expression.IsKind(SyntaxKind.StringLiteralExpression))
        {
            var literal = expression as LiteralExpressionSyntax;
            return literal.Token.ValueText;
        }
        else if (expression.IsKind(SyntaxKind.AddExpression))
        {
            var binaryExpression = expression as BinaryExpressionSyntax;
            return GetConstantValue(binaryExpression.Left, compilation) +
                GetConstantValue(binaryExpression.Right, compilation);
        }
        else
        {
            var model = compilation.GetSemanticModel(expression.SyntaxTree);
            var symbol = model.GetSymbolInfo(expression).Symbol;
            var defNode = symbol.DeclaringSyntaxReferences.First().GetSyntax();

            var valueClause = defNode.DescendantNodes().OfType<EqualsValueClauseSyntax>().FirstOrDefault();
            if (valueClause != null)
            {
                return GetConstantValue(valueClause.Value, compilation);
            }
            else
            {
                return "Unknown";
            }
        }
    }