Reflection Emit: how to Convert Attribute instance to CustomAttributeBuilder or CustomAttributeData

删除回忆录丶 提交于 2020-01-01 11:58:30

问题


I made a generator class that build a proxy class based on interface which implement the interface.

See my post on Build a Proxy class based on Interface without implementing it.

I'm familiar with CustomAttributeData.GetCustomAttributes(MemberInfo target), I used it when I read the Interface's members and succeed to import them to the proxy.

I want to inject additional attributes to generated class in run-time. I'm asking for attributes instances to inject them into the proxy.

For example:

A developer can pass this as a value: new ObsoleteAttribute("Demo", true), (it has an empty constructor, but properties are read only), and I want to convert it to:

return new CustomAttributeBuilder(
               attribute.GetType().GetConstructor(Type[] {typeof (string), typeof (bool)}),
               new object[] {"Demo", true},
               new FieldInfo[0], 
               new object[0]);

Remember, I can't tell what is given.


回答1:


This is not a general solution, but will work if you are willing to constrain the attributes you support to those with parameterless constructors and Read/Write Properties and Fields

CustomAttributeBuilder BuildCustomAttribute(System.Attribute attribute)
{
    Type type = attribute.GetType();
    var constructor = type.GetConstructor(Type.EmptyTypes);
    var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
    var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);

    var propertyValues = from p in properties
                         select p.GetValue(attribute, null);
    var fieldValues = from f in fields
                      select f.GetValue(attribute);

    return new CustomAttributeBuilder(constructor, 
                                     Type.EmptyTypes,
                                     properties,
                                     propertyValues.ToArray(),
                                     fields,
                                     fieldValues.ToArray());
}

To do a general solution, you could use expressions. That is more complicated, but would allow syntax like:

BuildCustomAttribute(() => new ObsoleteAttribute("Demo", true));

Parsing the expression to extract the constructor info and the parameters would be the complex part, but it can be done.

CustomAttributeBuilder BuildCustomAttribute(Expression<Action> exp)
{
    //extract ConstructorInfo from exp
    //extract ParameterValues from exp
    //extract Attribute Type from exp

    return new CustomAttributeBuilder(ConstructorInfo, ParameterValues);
}



回答2:


Thank you Joe,
I did find the Expression solution at Attribute Builder, thanks to your input.
I'm willing to work a little harder now to make other developers easier to use my Proxy.

I hoped it could be easier, and if I have the attribute instance, why can't I use it as is and apply the attribute?

If you have a solution without Expression, I'd love to hear about it.

Here is my solution with Expression based on Attribute Builder:

private CustomAttributeBuilder GetCustumeAttributeBuilder(Expression<Func<Attribute>> attributeExpression)
{
    ConstructorInfo constructor = null;
    List<object> constructorArgs = new List<object>();
    List<PropertyInfo> namedProperties = new List<PropertyInfo>();
    List<object> propertyValues = new List<object>();
    List<FieldInfo> namedFields = new List<FieldInfo>();
    List<object> fieldValues = new List<object>();

    switch (attributeExpression.Body.NodeType)
    {
        case ExpressionType.New:
            constructor = GetConstructor((NewExpression)attributeExpression.Body, constructorArgs);
            break;
        case ExpressionType.MemberInit:
            MemberInitExpression initExpression = (MemberInitExpression)attributeExpression.Body;
            constructor = GetConstructor(initExpression.NewExpression, constructorArgs);

            IEnumerable<MemberAssignment> bindings = from b in initExpression.Bindings
                                                        where b.BindingType == MemberBindingType.Assignment
                                                        select b as MemberAssignment;

            foreach (MemberAssignment assignment in bindings)
            {
                LambdaExpression lambda = Expression.Lambda(assignment.Expression);
                object value = lambda.Compile().DynamicInvoke();
                switch (assignment.Member.MemberType)
                {
                    case MemberTypes.Field:
                        namedFields.Add((FieldInfo)assignment.Member);
                        fieldValues.Add(value);
                        break;
                    case MemberTypes.Property:
                        namedProperties.Add((PropertyInfo)assignment.Member);
                        propertyValues.Add(value);
                        break;
                }
            }
            break;
        default:
            throw new ArgumentException("UnSupportedExpression", "attributeExpression");
    }

    return new CustomAttributeBuilder(
        constructor,
        constructorArgs.ToArray(),
        namedProperties.ToArray(),
        propertyValues.ToArray(),
        namedFields.ToArray(),
        fieldValues.ToArray());
}

private ConstructorInfo GetConstructor(NewExpression expression, List<object> constructorArgs)
{
    foreach (Expression arg in expression.Arguments)
    {
        LambdaExpression lambda = Expression.Lambda(arg);
        object value = lambda.Compile().DynamicInvoke();
        constructorArgs.Add(value);
    }
    return expression.Constructor;
}



回答3:


If I understand the question correctly, this should add a custom attribute to the generated type

public class CustomAttribute: System.Attribute
{
    public CustomAttribute()
    {
    }
}

TypeBuilder typeBuilder = module.DefineType(...)

....

typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(
    typeof(CustomAttribute).GetConstructor(Type.EmptyTypes), 
    Type.EmptyTypes, 
    new FieldInfo[0], 
    new object[0]));


来源:https://stackoverflow.com/questions/15895802/reflection-emit-how-to-convert-attribute-instance-to-customattributebuilder-or

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!