Reflection Emit:如何将属性实例转换为CustomAttributeBuilder或CustomAttributeData

5
我创建了一个生成器类,它基于实现接口的接口构建代理类。
请参阅我的帖子:不实现接口构建代理类
我熟悉 CustomAttributeData.GetCustomAttributes(MemberInfo target),当读取接口成员并成功导入它们到代理中时使用了它。
我想在运行时向生成的类注入附加属性。我正在请求属性实例以将它们注入到代理中。
例如:开发人员可以将此作为值传递:new ObsoleteAttribute("Demo", true)(它具有空构造函数,但属性是只读的),我想将其转换为:
return new CustomAttributeBuilder(
               attribute.GetType().GetConstructor(Type[] {typeof (string), typeof (bool)}),
               new object[] {"Demo", true},
               new FieldInfo[0], 
               new object[0]);

请记住,我不能确定给出的内容。


你是在询问如何向已生成的类(Type)添加属性,还是向你当前正在构建的类(TypeBuilder)添加属性? - svick
我目前正在构建它。 - Ofir
2
CustomAttributeBuilder 构造函数的重载有什么特别令人困惑的地方吗?我本来以为它们应该是不言自明的。 - kvb
3个回答

6
这不是通用解决方案,但如果您愿意将支持的属性限制为具有无参构造函数和读/写属性和字段的属性,则可以使用此方法。
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());
}

要做一个通用的解决方案,您可以使用表达式。这更加复杂,但可以允许类似这样的语法:

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

解析表达式以提取构造函数信息和参数可能是复杂的部分,但它是可行的。

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

    return new CustomAttributeBuilder(ConstructorInfo, ParameterValues);
}

2
再说一遍,这需要付出很多工作量,以减轻开发人员理解CustomAttributeBuilder的需要。 - Joe Enzminger
嗨,乔,干得好。我们需要修改这里的一行代码,以便只选择可以修改的属性。<code>var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x=>x.CanWrite).ToArray();</code> - Diceyus

4

谢谢Joe,
我在Attribute Builder找到了Expression解决方案,感谢您的建议。
我现在愿意更努力地工作,使其他开发人员更容易使用我的代理

我希望它可以更容易一些,如果我有属性实例,为什么不能直接使用并应用该属性呢?

如果您有不需要Expression的解决方案,我很乐意听取。

这是基于Attribute Builder的我的Expression解决方案:

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;
}

很棒的解决方案。这个应该被接受为答案,这段代码开始工作并且完成了它的任务。 - Genotypek

0
如果我理解正确的话,这应该向生成的类型添加自定义属性。
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]));

当我知道要添加哪个属性时,这很好。 我想允许开发人员添加任何属性。 - Ofir
1
我明白了。我相信你可以使用System.Reflection或System.Linq.Expressions编写代码,将“new ObsoleteAttribute(“Demo”,true)”转换为CustomAttributeBuilder实例,但这引出了一个问题,“为什么?”您可以轻松地将CustomAttributeBuilder实例的数组传递到代理生成方法中。虽然CustomAttributeBuilder在语法上更复杂,但您正在向代理生成器传递相同的信息。此外,在一般情况下,执行转换的代码将变得非常复杂-为什么要增加这种复杂性呢? - Joe Enzminger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接