Reflection Emit: how to Convert Attribute instance to CustomAttributeBuilder or CustomAttributeData
Asked Answered
F

3

5

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.

Fanfani answered 9/4, 2013 at 7:24 Comment(3)
Are you asking how to add attributes to already generated class (Type) or a class you're currently building (TypeBuilder)?Unformed
I'm currently building itFanfani
Is there anything particularly confusing about the CustomAttributeBuilder constructor overloads? I would have expected them to be self-explanatory.Formally
A
6

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);
}
Astrolabe answered 10/4, 2013 at 18:37 Comment(2)
Again, this is a lot of work to relieve your developer of the need to understand how CustomAttributeBuilder works....Astrolabe
Hi Joe, good work there. We need to modify one line here so that we are only picking the properties that can be modified.<code>var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x=>x.CanWrite).ToArray();</code>Hafler
F
4

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;
}
Fanfani answered 11/4, 2013 at 6:11 Comment(1)
Great solution. This one should be accepted answer, this code started to work in place and does it's job.Stonewall
A
0

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]));
Astrolabe answered 9/4, 2013 at 23:18 Comment(2)
This is nice when I know what attribute to add. I want to allow the developer to add any attribute.Fanfani
I see. I'm sure you could write code using System.Reflection or System.Linq.Expressions that could convert "new ObsoleteAttribute("Demo", true)" into a CustomAttributeBuilder instance, but that begs the question, "why?" You could just as easily pass an array of CustomAttributeBuilder instances into your proxy generating method. While CustomAttributeBuilder is syntactically more complicated, you are passing the same information to your proxy builder. Also, the code to do the conversion is going to become very complicated in the general case - why add that complexity?Astrolabe

© 2022 - 2024 — McMap. All rights reserved.