Nullable int в конструкторе атрибута или свойстве

Итак, у меня есть настраиваемый атрибут, пусть его вызывает MyCustomAttribute, у которого есть такой конструктор:

public MyCustomAttribute(int? i)
{
   // Code goes here
}

и объявляет свойство:

public int? DefaultProperty { get; set; }

Теперь, если бы я хотел использовать свойство, мне нужно было бы передать int или null, ну, что бы я ожидал.

Но это дает ошибку компилятора:

[MyCustomAttribute(1, DefaultProperty = 1)]
public int? MyProperty { get; set; }

и так:

[MyCustomAttribute(null,DefaultProperty = null)]
public int? MyProperty { get; set; }

Ошибка: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type как для конструктора, так и для свойства.

Почему это? Если я сменим конструктор на захват int, я могу перейти в 0, но не null, который портит цель значения (которое иногда может быть нулевым)

Ответы

Ответ 1

Причина в том, что, хотя оба 0 и null являются постоянными, фактическое выражение, необходимое для инициализации параметра конструктора, не является. Оба требуют преобразования в качестве действительного выражения int?. Под капотом он по существу генерирует следующие

[MyCustomAttribute(new Nullable<int>(0))]

Это выражение, не являющееся константой, не является законным как аргумент атрибута

EDIT

dtb спросил, является ли это незаконным в значениях атрибутов, почему это законно иметь параметр по умолчанию для параметра с нулевым значением?

void Example(int? x = null);

Что вам нужно учитывать, так это то, что/что интерпретирует значения.

Для аргументов атрибутов значение интерпретируется CLR. Правила, касающиеся значений правовых атрибутов, с тех пор не изменились. Nullable не существует, поэтому CLR не понимает шаблоны инициализации с нулевым значением.

Для аргументов по умолчанию вау интерпретируется компилятором на сайте вызова метода. Компиляторы понимают значения с нулевым значением и имеют немного больше возможностей для работы, поскольку они могут создавать непостоянные выражения на сайте вызова на основе значений в IL.

Как это работает? Сначала компилятор будет фактически кодировать константы и null по-разному в IL

// x = 0 
[DefaultParameterValue(0)]
// x = null
[DefaultParameterValue(null)]

На сайте вызова компилятор проверяет эти значения и создает соответствующее (непостоянное) выражение для значения параметра.

Ответ 2

Я знаю, почему возникает вопрос, но для разработчиков, которые перенаправляются на эту страницу и ищут ответа. Это мое решение.

public class MyCustomAttribute: Attribute
{
    private Nullable<bool> myBoolean = null;

    public bool MyBooleanProperty { get { return this.myBoolean.GetValueOrDefault(); } set { this.myBoolean = value; } }
    public bool IsMyBooleanPropertySpecified { get { return this.myBoolean != null; } }
}

Ответ 3

Правило для использования атрибутов заключается в том, что тип параметров, которые они принимают, может быть объявлен с помощью ключевого слова const - то есть всех элементарных типов (int, char и т.д.) и string. Ключевое слово new может использоваться в списке параметров атрибутов

Вы не можете использовать такой атрибут:

[Custom(new object())]
class Class {
}

или даже так (даже если DateTime - тип значения):

[Custom(new DateTime(2001,1,1))]
class Class {
}

Следовательно, использование Nullable<T> также не допускается, поскольку передача null или 1 эквивалентна этому:

[Custom(new Nullable<int>())]
//[Custom(null)]
class Class {
}
[Custom(new Nullable<int>(1))]
//[Custom(1)]
class Class {
}

Ответ 4

Я думаю, что было бы легко забыть проверить IsPropertyXSet перед доступом к переменной. Я думаю, что лучше использовать значение nullable. Итак, вот возможный способ структурирования, сохраняя при этом значение nullable:

public class FooAttribute : Attribute {
        public bool? SomeFlag { get; set; }

        public bool SetSomeFlag {
        get {
            throw new Exception("should not be called"); // required for property visibility
        }
        set {
            SomeFlag = value;
        }
    }
}

[Foo(SetSomeFlag=true)]
public class Person {
}

[Foo]
public class Person2 { // SetSomeFlag is not set
}

bool? b1 = ((FooAttribute)typeof(Person).GetCustomAttributes(typeof(FooAttribute), false)[0]).SomeFlag; // b1 = true
bool? b2 = ((FooAttribute)typeof(Person2).GetCustomAttributes(typeof(FooAttribute), false)[0]).SomeFlag; // b2 = null

Ответ 5

Я знаю, что это старый вопрос, но никто не ответил на , почему Nullable < > не разрешено. Этот окончательный ответ на это заключается в документации для ошибки компилятора CS0655:

Позиционные и именованные параметры для класса атрибута ограничены следующие типы параметров атрибута:

  • Один из следующих типов: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong или ushort.
  • Объект типа.
  • Тип System.Type.
  • Тип перечисления при условии, что он имеет общедоступную доступность и что любые типы, в которых он вложен, также имеют общедоступную доступность.
  • Одномерные массивы предыдущих типов.

Эта страница документации предназначена для Visual Studio 2008, но я не слышал о каких-либо недавних изменениях в этой области.

Ответ 6

Парень покупает Ferrari, но какой-то дурак поставил губернатора (приспособление, ограничивающее скорость автомобиля) до 30 миль в час. Парень не может изменить губернатора на что-то более разумное, поэтому он разрывает его. Теперь Ferrari может двигаться так же быстро, как двигатель может мышцы.

Корпорация Майкрософт не позволяет использовать свойства nullables в качестве свойств настраиваемых атрибутов. Тем не менее, Microsoft позволяет использовать строки, которые могут быть нулевыми, как свойства пользовательских атрибутов. Так полностью уничтожьте ограничение. Замените нулевую int на строку. Несомненно, строка еще менее ограничительна, чем nullable int, поскольку она может иметь нецелые значения, такие как "bob", но что цена, которую вы платите за Microsoft, закручиваете пользовательские атрибуты, язык, для детализации реализации в CLR, должно быть неактуальным.

Вот мой пример.

public abstract class Contract : Attribute, IContract
{
    public abstract void Check (Object root, String path, Object valueAtPath);
}

public sealed class DecimalPlacesContract : Contract
{
    public String MinimumMantissaCount
    {
        get
        {
            return minimumMantissaCount?.ToString();
        }

        set
        {
            minimumMantissaCount = value == null ? (int?) null : Int32.Parse(value);
        }
    }

    public String MaximumMantissaCount
    {
        get
        {
            return maximumMantissaCount?.ToString();
        }

        set
        {
            maximumMantissaCount = value == null ? (int?) null : Int32.Parse(value);
        }
    }

    public String MinimumSignificantDigitCount
    {
        get
        {
            return minimumSignificantDigitCount?.ToString();
        }

        set
        {
            minimumSignificantDigitCount = value == null ? (int?) null : Int32.Parse(value);
        }
    }

    public String MaximumSignificantDigitCount
    {
        get
        {
            return maximumSignificantDigitCount?.ToString();
        }

        set
        {
            maximumSignificantDigitCount = value == null ? (int?) null : Int32.Parse(value);
        }
    }

    private int? minimumMantissaCount;
    private int? maximumMantissaCount;
    private int? minimumSignificantDigitCount;
    private int? maximumSignificantDigitCount;



    public override void Check (Object root, String path, Object valueAtPath)
    {
        decimal? value = valueAtPath as decimal?;

        int mantissaCount = DecimalMisc.GetMantissaDigitCount(value ?? 0);
        int significantDigitCount = DecimalMisc.GetSignificantDigitCount(value ?? 0);

        if (value == null ||
            mantissaCount < minimumMantissaCount ||
            mantissaCount > maximumMantissaCount ||
            significantDigitCount < minimumSignificantDigitCount ||
            significantDigitCount > maximumSignificantDigitCount)
        {
            throw new ContractException(this, root, path, valueAtPath);
        }
    }
}

Здесь, как использовать свойства настраиваемого атрибута.

    private class Dollar
    {
        [DecimalPlacesContract (MinimumMantissaCount = "0", MaximumMantissaCount = "2")]
        public decimal Amount { get; set; }
    }

    private class DollarProper
    {
        [DecimalPlacesContract (MinimumSignificantDigitCount = "2", MaximumSignificantDigitCount = "2")]
        public decimal Amount { get; set; }
    }

Просто добавьте кавычки вокруг значений. Неопределенные свойства по умолчанию равны нулю.

Любое неправильное значение, подобное "bob", вызовет исключение FormatException, когда вы вызываете GetCustomAttributes или GetCustomAttribute из экземпляра Type или PropertyInfo. Конечно, было бы неплохо иметь проверку времени компиляции с помощью nullable int, но это достаточно хорошо. Так что разорвите этот губернатор.