背景
我正在尝试使用 C# 源代码生成器。这主要是一个实验项目,但目标是创建一个库,以帮助用户使用键值对格式编写日志。在编译时应该有一组预定义的公共键。enum
在用户声明 an并用特殊属性装饰它之前,库不会知道这些是什么。成员可以选择用enum
声明Type
键是什么的附加属性进行修饰(如果该属性不存在,我们假设它是一个字符串)。这是enum
我正在使用的 s:
[AttributeUsage(AttributeTargets.Enum)]
public class LogKeysAttribute : Attribute { }
[System.AttributeUsage(System.AttributeTargets.Field)]
public class LogMemberAttribute : Attribute
{
public Type FieldType { get; set; }
}
所以,作为一个例子,我们可能有这样的事情:
[LogKeys]
public enum CommonKeys
{
[LogMember(FieldType = typeof(int))] FirstAsInt = 1,
[LogMember(FieldType = typeof(string))] SecondAsString = 2,
ThirdAsImpliedString = 5,
}
现在库要做的是创建一个像这样的新方法:
public void Log(int? firstAsInt=null, string secondAsString=null, string thirdAsImpliedString = null)
{
...
}
问题
到目前为止,我已经按照我想要的方式完成了所有工作,但是我对读取FieldType
. LogMemberAttribute
我可以看到是否LogMember
存在,并且在调试器中,我可以看到它被设置为int
or string
,但在代码中我无法弄清楚如何将其拉出并使用它。
源代码
这是我的方法的(简化版本)Execute
(注意:我意识到这里有些东西不是最佳的。没关系 - 现在我们只担心从属性中读取):
public void Execute(GeneratorExecutionContext context)
{
var compilation = context.Compilation;
//Declare our attributes that will be used
//I had hoped adding it here would make the type available to use below, but no such luck
var attributeDefinitionsSrc = @"
using System;
namespace LogWrapper
{
[AttributeUsage(AttributeTargets.Enum)]
public class LogKeysAttribute : Attribute { }
[System.AttributeUsage(System.AttributeTargets.Field)]
public class LogMemberAttribute : Attribute
{
public Type FieldType { get; set; }
}
}";
//Add the attributes to the compilation
context.AddSource("CustomAttributes.cs", attributeDefinitionsSrc);
var attribSyntaxTree = CSharpSyntaxTree.ParseText(attributeDefinitionsSrc, (CSharpParseOptions)context.ParseOptions);
compilation = compilation.AddSyntaxTrees(attribSyntaxTree);
//Now we can get those symbols to use later
var keyAttribute = compilation.GetTypeByMetadataName("LogWrapper.LogKeyAttribute");
var memberTypeAttr = compilation.GetTypeByMetadataName("LogWrapper.LogMemberAttribute");
//targets will be anything that is an Enum declaration
var targets = compilation.SyntaxTrees
.SelectMany(x => x.GetRoot()
.DescendantNodesAndSelf()
.OfType<EnumDeclarationSyntax>());
foreach (var t in targets)
{
//bonus points if you can explain to me what the "SemanticModel" is and how it differs from a syntax tree.
//I see you can view the syntax tree at sharplab.io. Is there a place/way to view the semantic model?
var targetType = ModelExtensions
.GetDeclaredSymbol(compilation.GetSemanticModel(t.SyntaxTree), t);
//We are only interested in the first enum that has our KeyAttribute associated
if (targetType != null &&
targetType.GetAttributes().Any(x => x.AttributeClass.Equals(keyAttribute)) &&
targetType is ITypeSymbol its)
{
//Here we get all the enum members (FirstAsInt, SecondAsString, etc.)
//They are sorted by their enum value (FirstAsInt is 1, so it goes first)
var iSymbolList = its.GetMembers()
.Where(x=>x is IFieldSymbol ifs && ifs.HasConstantValue)
.OrderBy(x => ((IFieldSymbol)x).ConstantValue)
.ToList();
//Next we iterate over all the members
foreach (var sym in iSymbolList)
{
//If this member has a LogMember attribute associated with it,
// then we get that attribute.
var attrib = sym.GetAttributes()
.FirstOrDefault(x=>x.AttributeClass?.Equals(memberTypeAttr) == true);
if (attrib != default)
{
//There was a LogMember attribute. This means I want to read the
// value of FieldType so I know what Type it should be.
// **** THIS IS THE PART THAT I CAN'T DO *****
//Debugger shows list of 1 with value "{[FieldType, {Microsoft.CodeAnalysis.TypedConstant}]}"
var namedArgsList = attrib.NamedArguments;
//Debugger shows an empty list
var constArgsList = attrib.ConstructorArguments;
//Debugger shows "{[FieldType, {Microsoft.CodeAnalysis.TypedConstant}]}"
var firstArg = namedArgsList.FirstOrDefault(x => x.Key == "FieldType");
//Debugger shows value "{int}" and it shows the type as:
//"object { Microsoft.CodeAnalysis.CSharp.Symbols.PublicModel.NonErrorNamedTypeSymbol}"
//It seems the data I want is here, but I can't get to it...
var valAsObj = firstArg.Value.Value;
//Debugger shows null. We can't cast to a Type.
var asType = valAsObj as Type;
}
//else assume string (code removed)
}
//by this point we have all the data we need to generate the code
break;
}
}
那么是否有可能获取这些数据?恐怕我对 SyntaxTrees 和诸如此类的东西不是很好,所以似乎我可以做一些事情来给我想要的东西,但我只是不知道那是什么。