[英]Converting AttributeData into a known attribute type Roslyn
So I have a known attribute type from my code, say, something like this:所以我的代码中有一个已知的属性类型,比如:
[AttributeUsage(AttributeTargets.Method)]
public class AliasAttribute : Attribute
{
public string Alias;
public AliasAttribute(string alias)
{
Alias = alias;
}
}
I also have a method decorated with this attibute, which I'm getting as an IMethodSymbol
in Roslyn:我也有一个用这个属性装饰的方法,我在 Roslyn 中得到了一个
IMethodSymbol
:
// E.g. code which Roslyn gets to analyze
[Alias("Hello")] public void World() {}
// Actual Roslyn code
var method = (IMethodSymbol) compilation.GetSymbolsWithName("World").Single();
I am able to retrieve the attribute data using code similar to this:我可以使用与此类似的代码检索属性数据:
var attributeData = method.GetAttributes().Single();
Now I want to use some simple built-in method of casting this AttributeData
to the known type, eg like this:现在我想使用一些简单的内置方法将此
AttributeData
转换为已知类型,例如:
AliasAttribute alias = attributeData.MapToType<AliasAttribute>();
The way I'm doing it currently is complicated: it involves instantiating the attribute type using reflection.我目前这样做的方式很复杂:它涉及使用反射来实例化属性类型。 However, I did not account for a whole lot of edge-cases, like
params
parameters in a generic way.但是,我并没有考虑到很多极端情况,比如以通用方式的
params
参数。 See the code in the answer below.请参阅下面答案中的代码。 However, it is too complicated and I wonder if there is a simpler way of doing this.
但是,它太复杂了,我想知道是否有更简单的方法可以做到这一点。
As noted in the question, here is my solution, using reflection.如问题所述,这是我的解决方案,使用反射。
public static T MapToType<T>(this AttributeData attributeData) where T : Attribute
{
T attribute;
if (attributeData.AttributeConstructor != null && attributeData.ConstructorArguments.Length > 0)
{
attribute = (T) Activator.CreateInstance(typeof(T), attributeData.GetActualConstuctorParams().ToArray());
}
else
{
attribute = (T) Activator.CreateInstance(typeof(T));
}
foreach (var p in attributeData.NamedArguments)
{
typeof(T).GetField(p.Key).SetValue(attribute, p.Value.Value);
}
return attribute;
}
public static IEnumerable<object> GetActualConstuctorParams(this AttributeData attributeData)
{
foreach (var arg in attributeData.ConstructorArguments)
{
if (arg.Kind == TypedConstantKind.Array)
{
// Assume they are strings, but the array that we get from this
// should actually be of type of the objects within it, be it strings or ints
// This is definitely possible with reflection, I just don't know how exactly.
yield return arg.Values.Select(a => a.Value).OfType<string>().ToArray();
}
else
{
yield return arg.Value;
}
}
}
If anybody finds the code from above useful, I have also defined a couple of convenient methods:如果有人发现上面的代码有用,我还定义了几个方便的方法:
public static bool TryGetAttributeData(this ISymbol symbol, ISymbol attributeType, out AttributeData attributeData)
{
foreach (var a in symbol.GetAttributes())
{
if (SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType))
{
attributeData = a;
return true;
}
}
attributeData = default;
return false;
}
public struct AttributeSymbolWrapper<T>
{
public INamedTypeSymbol symbol;
private static INamedTypeSymbol GetKnownSymbol(Compilation compilation, System.Type t)
{
return (INamedTypeSymbol) compilation.GetTypeByMetadataName(t.FullName);
}
public void Init(Compilation compilation)
{
symbol = GetKnownSymbol(compilation, typeof(T));
}
}
public static bool TryGetAttribute<T>(this ISymbol symbol, AttributeSymbolWrapper<T> attributeSymbolWrapper, out T attribute) where T : System.Attribute
{
if (TryGetAttributeData(symbol, attributeSymbolWrapper.symbol, out var attributeData))
{
attribute = attributeData.MapToType<T>();
return true;
}
attribute = default;
return false;
}
public static bool HasAttribute(this ISymbol symbol, ISymbol attributeType)
{
foreach (var a in symbol.GetAttributes())
{
if (SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType))
{
return true;
}
}
return false;
}
Then, somewhere global at the start of the program, save the types that you'll want to cast into:然后,在程序开始时的某个全局位置,保存您要转换为的类型:
public static class RelevantSymbols
{
public static AttributeSymbolWrapper<AliasAttribute> AliasAttribute;
public static void Init(Compilation compilation)
{
AliasAttribute.Init(compilation);
}
}
Finally, you can use it like this, without repeating the attribute type in the type argument:最后,您可以这样使用它,而无需在 type 参数中重复属性类型:
var method = (IMethodSymbol) compilation.GetSymbolsWithName("World").Single();
if (method.TryGetAttribute(RelevantSymbols.AliasAttribute, out var attribute))
{
// do something with the attibute data
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.