1
votes

While creating types and properties with TypeBuilder and PropertyBuilder I need to add custom attributes. However, the API I'm trying to create is abstracting as much reflection stuff as possible so in order to add attributes to types or properties the user supplies an actual attribute.

But as attributes can't be added to TypeBuilder or PropertyBuilder (as far as I've found) I need to create instances of CustomAttributeBuilder (which can be added) with matching values from the supplied attributes. I use the below method to do this and it works fine except for when an attribute has a nullable property.

If an attribute contains a nullable property an ArgumentException is thrown saying "An invalid type was used as a custom attribute constructor argument, field or property." when the last line of the method is executed.

private static CustomAttributeBuilder CreateAttributeWithValuesFromTemplate(
    Attribute attribute)
{
    var propertiesWithValues = new List<PropertyInfo>();
    var nonNullPropertyValues = new List<Object>();

    if (attribute != null)
    {
        var properties = GetWritableProperties(attribute);

        object[] propertyValues = GetPropertyValues(attribute, properties);


        for (int i = 0; i < properties.Length; i++)
        {
            if (propertyValues[i] == null)
                continue;

            propertiesWithValues.Add(properties[i]);
            nonNullPropertyValues.Add(propertyValues[i]);
        }
    }

    ConstructorInfo constructor;
    if (attributeSpecification.Constructor != null)
        constructor = attributeSpecification.Constructor;
    else
        constructor = attributeSpecification.Type.GetConstructor(new Type[] { });

    object[] constructorParams;
    if (attributeSpecification.Constructor != null)
        constructorParams = attributeSpecification.ConstructorParameters;
    else
        constructorParams = new object[] {};


    return new CustomAttributeBuilder(constructor, constructorParams,
        propertiesWithValues.ToArray(), nonNullPropertyValues.ToArray());
}

I've tried to modify the method to check if a property is of a nullable type but I haven't found a way to actually supply a value that is nullable, ie the below code (which is just a test) doesn't work as nullableValue.GetType() will return Int32.

if(properties[i].PropertyType.IsGenericType
&& properties[i].PropertyType.GetGenericTypeDefinition()== typeof(Nullable<>)) {
  Nullable<int> nullableValue = new Nullable<int>();
  nonNullPropertyValues.Add(nullableValue);
} else {
  nonNullPropertyValues.Add(propertyValues[i]);
}

Any solutions or pointers in the right direction would be appreciated.

1
Erm, Type/PropertyBuilder have a SetCustomAttribute() method. Does that change your question? - Hans Passant
Unfourtunately not as that method requires a CustomAttributeBuilder and not an attribute. It's actually that method I'm calling with the value returned from the method shown above. - Joel Abrahamsson
Seems it's not possible to do what I want to do according to msdn.microsoft.com/en-us/library/kb87eks2%28v=vs.95%29.aspx. Any bright ideas for workarounds are most welcome. The only thing I can come up with is to later set the value by retrieving the attribute from the TypeBuilder/PropertyBuilder but that causes another runtime exception as they don't support the GetCustomAttributes method. - Joel Abrahamsson

1 Answers

0
votes

why dont you check the property, not the value.

Inside your GetWritableProperties where you are getting The PropertyInfo collection. You can at once get the properties and values like this:

creat a class to hold the PropertyInfo and Value

public class InfoAndValue
{
    public PropertyInfo Info { get; set; }
    public Object Value { get; set; }
}

then change your method GetWritableProperties to:

static IEnumerable<InfoAndValue> GetWritableProperties(Attribute attribute)
{
    var collection = from p in attribute.GetType().GetProperties()
                     where !p.PropertyType.Name.StartsWidth("Nullable`1")
                     select new InfoAndValue { 
                                    Info = p, 
                                    Value = p.GetValue(attribute, null) 
                                };
    return collection.Where(x => x.Value != null);
}

With this you save alot of code.