问题
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