[英]Converting AttributeData into a known attribute type Roslyn
所以我的代碼中有一個已知的屬性類型,比如:
[AttributeUsage(AttributeTargets.Method)]
public class AliasAttribute : Attribute
{
public string Alias;
public AliasAttribute(string alias)
{
Alias = alias;
}
}
我也有一個用這個屬性裝飾的方法,我在 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();
我可以使用與此類似的代碼檢索屬性數據:
var attributeData = method.GetAttributes().Single();
現在我想使用一些簡單的內置方法將此AttributeData
轉換為已知類型,例如:
AliasAttribute alias = attributeData.MapToType<AliasAttribute>();
我目前這樣做的方式很復雜:它涉及使用反射來實例化屬性類型。 但是,我並沒有考慮到很多極端情況,比如以通用方式的params
參數。 請參閱下面答案中的代碼。 但是,它太復雜了,我想知道是否有更簡單的方法可以做到這一點。
如問題所述,這是我的解決方案,使用反射。
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;
}
}
}
如果有人發現上面的代碼有用,我還定義了幾個方便的方法:
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;
}
然后,在程序開始時的某個全局位置,保存您要轉換為的類型:
public static class RelevantSymbols
{
public static AttributeSymbolWrapper<AliasAttribute> AliasAttribute;
public static void Init(Compilation compilation)
{
AliasAttribute.Init(compilation);
}
}
最后,您可以這樣使用它,而無需在 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.