reflection - CustomAttributeBuilder

Hi,

I am looking for a way to create a CustomAttributeBuilder of the same type and with the same values as a CustomAttribute that has been accessed via reflection (i.e. someMember.GetCustomAttributes()).

I can't find a way of getting all the info i need to do this. For example, if i use the constructor:
CustomAttributeBuilder (ConstructorInfo, Object[], PropertyInfo[], Object[])
I can populate the arrays for the last two arguments as follows:

Type attributeType = customAttribute.GetType();
List<PropertyInfo> newProperties = new List<PropertyInfo>();
List<object> newPropertyValues = new List<object>();
foreach (PropertyInfo p in attributeType.GetProperties()) {
if (p.CanWrite) {
newProperties.Add(p);
newPropertyValues.Add(p.GetValue(customAttribute, null));
}
}
PropertyInfo[] newPropertyInfoArray = newProperties.ToArray();
Object[] newPropertyValueArray = newPropertyValues.ToArray();

However, i cannot find a way to reliably create the first two args to the CustomAttributeBuilder constructor. Some custom attributes have propeties that can only be set through the CustomAttributeBuilder constrcutor. Initial attempts have roughly taken the form:

ConstructorInfo ctor = attributeType.GetConstructors()[0];
ParameterInfo[] ctorParams = ctor.GetParameters();
Object[] ctorArgs = new Object[ctorParams.Length] ;
for (int i = 0; i < ctorParams.Length; i++) {
//PUT SOME VALUE OF THE CORRECT TYPE INTO ctorArgs
}

The names of parameters in the constructors of custom attributes do not necessarily match the name of properties or fields contained in the custom attribute so relying on names doesn't work. Simply looking for propeties or fields in the attribute of the same type as the required parameter often works but is error prone. There is also the question of which of the original custom attributes constructors i should be using in the first place.

Can anyone point me in the right direction? Any help much appreciated,

colin

[2099 byte] By [coconut] at [2007-12-22]
# 1

This is pretty tricky, because what you are really asking is 'Can I reverse-engineer what the constructor does, in order to get back the input parameters?' The answer to that is no.

I've had a pretty good guess at getting the parameters, which is to assume that there are properties (I guess you could do it with fields too) whose names match the names of the arguments to the constructor. I choose the constructor with the most parameters, and set all the parameters in there. The complete code looks something like this:

ConstructorInfo attrCons = null;
foreach (ConstructorInfo cInfo in attr.GetType().GetConstructors())
{
if (attrCons == null || attrCons.GetParameters().Length < cInfo.GetParameters().Length)
attrCons = cInfo;
}
if (attrCons != null)
{
object[] args = new object[attrCons.GetParameters().Length];
int pos = 0;
foreach (ParameterInfo consParamInfo in attrCons.GetParameters())
{
PropertyInfo attrPropInfo = attr.GetType().GetProperty(consParamInfo.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (attrPropInfo != null)
{
args[pos] = attrPropInfo.GetValue(attr,
null);
}
else
{
args[pos] =
null;
}
++pos;
}
List<PropertyInfo> propList = new List<PropertyInfo>();
List<object> propValueList = new List<object>();
foreach (PropertyInfo attrPropInfo in attr.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance))
{
if (attrPropInfo.CanWrite)
{
object defaultValue = null;
object[] defaultAttrs = attrPropInfo.GetCustomAttributes(typeof(DefaultValueAttribute), true);
if (defaultAttrs != null && defaultAttrs.Length > 0)
{
defaultValue = ((
DefaultValueAttribute)defaultAttrs[0]).Value;
}
object value = attrPropInfo.GetValue(attr, null);
if (value != defaultValue)
{
propList.Add(attrPropInfo);
propValueList.Add(value);
}
}
}
CustomAttributeBuilder attrBuilder = new CustomAttributeBuilder(attrCons, args, propList.ToArray(), propValueList.ToArray());
}

Hope that helps (if you haven't given up on this already!)

MatthewWright at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 2

Thanks Matthew - I thought i was utterly alone is even seeing a problem here. The solution I currently use is posted at the end of this message.

Before continuing here's a thought, you say that "what you are really asking is 'Can I reverse-engineer what the constructor does, in order to get back the input parameters?' The answer to that is no." Actually, i think the the answer to that is yes. It would be possible to get the array of bytes for the body of the constructor, reverse engineer it to recover the IL instructions, and then follow the routes of the input parameters through these instructions in order to determine which input parameter determines the value of which field/property. In fact, you would have to do this for all the constructors because it is possible that even the constructor with the most parameters does not set a field/property that is set in the instance you wish to replicate. I think a solution that can be guaranteed as 100% reliable would have to be based on this sort of approach. However, this wasn't the kind of solution i went for (it would be a nightmare).

The solution i went for looks alot like yours. If i remember rightly, at one point I had a solution that was almost exactly like yours. It took the constructor with the most arguments and made guesses based on the names. But the solution I use now takes the constructor with the fewest arguments and makes guesses based on the Type. Unfortunately, I don't remember exactly why I made this change, but it was as a result of testing it with a range of custom attributes. I think the constructor with the fewest arguments is likely to contain only the read-only properties and it is only these that there is a problem with. As you know, the write-able properties can be dealt with reliably by the last two arguments to CustomAttributeBuilder's constructor.

It's true that my current way does still sometimes get things wrong. I guess which solution seems best depends on what you use as your test cases. We can probably agree that getting it right for Attributes that are already defined in the .NET framework is most important? In particular, i would like a solution that works with the attributes used for XML serialization.

I found that matching names wasn't that reliable. For example, try your method on all the custom attributes for an assembly compiled by Visual Studio; it barfs on ComVisibleAttribute. (See http://msdn2.microsoft.com/en-us/library/system.runtime.interopservices.comvisibleattribute_members.aspx to know why.) A good improvement of your method would be so that it doesn't cause a null pointer exception if the param name doesn't match the property name.

What happens if you plug my solution into your code? I'd be interested to hear about situations where you're way works and mine doesn't. Perhaps we could collaborate on finding the best solution, for example, I didn't make use of DefaultValueAttribute which you use in your solution, and there may be advatanges to using it. Alternatively, maybe it's not really as important to you as it is to me - in which case, I envy you.

cheers,

colin

STUPID LIGHTBULB EMOTICON BREAKS CODE, should be open square bracket then i then close square bracket

public static CustomAttributeBuilder CustomAttributeToCustomAttributeBuilder(Object attr) {

Type attType = attr.GetType();

//hacky... not that easy to construct CustomAttributeBuilder with same values as original

//(perhaps impossible to do with 100% relaiability in current version of .NET? see http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=599525&SiteID=1

ConstructorInfo ctor = attType.GetConstructors()[0];

//get the constructor with the least params?

foreach (ConstructorInfo c in attType.GetConstructors()) {

if (c.GetParameters().Length < ctor.GetParameters().Length) ctor = c;

}

ParameterInfo[] ctorParams = ctor.GetParameters();

Object[] ctorArgs = new Object[ctorParams.Length];

PropertyInfo[] properties = attType.GetProperties();

Object[] propertyValues = new Object[properties.Length];

for (int i = 0; i < properties.Length; i++) {

Object propertyValue = propertiesIdea.GetValue(attr, null);

propertyValuesIdea = propertyValue;

bool propertyUsedInConstructor = false;

for (int j = 0; j < ctorArgs.Length; j++) {

if (!propertyUsedInConstructor

&& ctorArgs[j] == null

&& ctorParams[j].ParameterType.Equals(propertiesIdea.PropertyType)) {

ctorArgs[j] = propertyValue;

propertyUsedInConstructor = true;

}

}

}

FieldInfo[] fields = attType.GetFields();

Object[] fieldValues = new Object[fields.Length];

for (int i = 0; i < fields.Length; i++) {

fieldValuesIdea = fieldsIdea.GetValue(attr);

}

List<PropertyInfo> newProperties = new List<PropertyInfo>();

List<object> newPropertyValues = new List<object>();

foreach (PropertyInfo p in attType.GetProperties()) {

if (p.CanWrite) {

newProperties.Add(p);

newPropertyValues.Add(p.GetValue(attr, null));

}

}

PropertyInfo[] newPropertyInfoArray = newProperties.ToArray();

Object[] newPropertyValueArray = newPropertyValues.ToArray();

return new CustomAttributeBuilder(ctor, ctorArgs, newPropertyInfoArray, newPropertyValueArray);//, fields, fieldValues);

}

coconut at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 3

I guess that in theory it might be possible to reverse engineer the constructor through IL, but it kind of implies that you know which constructor was used in the first place, and I'm still not convinced that it would ever be perfect. I will gladly bow to your greater knowledge when you post the code to do it

The problem I had with using the constructor with the least params was that in some cases you cannot set the properties by any other method, for example the DescriptionAttribute. The property there is read only, so the only way to set the description in that attribute is through the constructor, but there is a default constructor (i.e. no parameters) which your code woudl pick up.

I'll try to use some of your code with mine to set the parameters on some attributes that I'm using: at the moment I have a horrible kludge in my code to handle TypeConverterAttribute, because the parameter name on the constructor doesn't match with any of the properties in the class. There's also two constructors in that class with one parameter, so I'm not sure if I can guarantee to get the one I want.

The way I see it the whole thing is a bit of a kludge. The only real way to fix it that I can see is some internal changes around attributes, but I don't expect anything to happen just to sort this problem out!

Cheers,

Matthew

MatthewWright at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 4

>>I will gladly bow to your greater knowledge when you post the code to do it

don't hold you're breath...

>>The way I see it the whole thing is a bit of a kludge. The only real way to fix it that I can see is some internal changes around >>attributes, but I don't expect anything to happen just to sort this problem out!

I agree

>>The problem I had with using the constructor with the least params was that in some cases you cannot set the properties by any >>other method, for example the DescriptionAttribute. The property there is read only, so the only way to set the description in that >>attribute is through the constructor, but there is a default constructor (i.e. no parameters) which your code woudl pick up.

Again, you're right. I said I couldn't remember why I'd changed to using the constructor with the least parameters but having looked at it again I think the code I posted is a kludge i ended up with to deal with a speciifc situation... you're right that the one with the fewest args isn't the one to go for.

>>I'll try to use some of your code with mine to set the parameters on some attributes that I'm using

good luck but i'm not actually that confidient in the code a posted earlier now. You may be wasting your time by trying to use it.

>>at the moment I have a horrible kludge in my code to handle TypeConverterAttribute, because the parameter name on the >>constructor doesn't match with any of the properties in the class. There's also two constructors in that class with one parameter, >>so I'm not sure if I can guarantee to get the one I want.

yes, i guess nobody thought we would want to do this... but we do. I would really like to find a general solution. I've started thinking about a brute force solution that tries all possible combinations until it gets one that it is correct. It is possible to check whether you're getting it right. It could store correct mappings to cut down the amount of time it takes.

Anyway, have to go now, may continue this discussion later,

cheers,

col

coconut at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Base Class Library...

.NET Development

Site Classified