简体   繁体   English

将 AttributeData 转换为已知的属性类型 Roslyn

[英]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;
        }
    }
}

More helper methods更多辅助方法

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM