При создании типов и свойств с помощью TypeBuilder и PropertyBuilder мне нужно добавить пользовательские атрибуты. Однако API, который я пытаюсь создать, абстрагирует как можно больше отражений, поэтому для добавления атрибутов к типам или свойствам пользователь предоставляет фактический атрибут.

Но поскольку атрибуты не могут быть добавлены в TypeBuilder или PropertyBuilder (насколько мне известно), мне нужно создать экземпляры CustomAttributeBuilder (которые можно добавить) с соответствующими значениями из предоставленных атрибутов. Для этого я использую приведенный ниже метод, и он отлично работает, за исключением случаев, когда атрибут имеет свойство, допускающее значение NULL.

Если атрибут содержит свойство, допускающее значение NULL, создается исключение ArgumentException, говорящее: «Недопустимый тип использовался в качестве аргумента, поля или свойства конструктора настраиваемого атрибута». когда выполняется последняя строка метода.

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

Я попытался изменить метод, чтобы проверить, имеет ли свойство тип, допускающий значение NULL, но я не нашел способа фактически указать значение, допускающее значение NULL, т.е. приведенный ниже код (который является просто тестом) не работает поскольку nullableValue.GetType() вернет 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]);
}

Любые решения или указатели в правильном направлении будут оценены.

1
Joel Abrahamsson 6 Мар 2011 в 03:27
Эм, у Type/PropertyBuilder есть метод SetCustomAttribute(). Это меняет ваш вопрос?
 – 
Hans Passant
6 Мар 2011 в 03:34
К сожалению, нет, поскольку для этого метода требуется CustomAttributeBuilder, а не атрибут. На самом деле это тот метод, который я вызываю со значением, возвращаемым из метода, показанного выше.
 – 
Joel Abrahamsson
6 Мар 2011 в 14:20
Кажется, невозможно сделать то, что я хочу, в соответствии с msdn.microsoft.com/en-us/library/kb87eks2%28v=vs.95%29.aspx. Приветствуются любые яркие идеи обходных путей. Единственное, что я могу придумать, это позже установить значение, извлекая атрибут из TypeBuilder/PropertyBuilder, но это вызывает другое исключение во время выполнения, поскольку они не поддерживают метод GetCustomAttributes.
 – 
Joel Abrahamsson
6 Мар 2011 в 14:54

1 ответ

Почему бы вам не проверить свойство, а не значение.

Внутри вашего GetWritableProperties, где вы получаете коллекцию PropertyInfo. Вы можете сразу получить такие свойства и значения:

Создайте класс для хранения PropertyInfo и Value

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

Затем измените свой метод GetWritableProperties на:

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

При этом вы экономите много кода.

0
Arturo Martinez 10 Мар 2011 в 18:19
Вы можете добавить дополнительные условия в оператор where, например p.PropertyType.IsPrimitive
 – 
Arturo Martinez
10 Мар 2011 в 18:08