ограничения типа именованного параметра

#c# #.net-3.5 #custom-attributes

#c# #.net-3.5 #пользовательские атрибуты

Вопрос:

Я разрабатываю пользовательский класс атрибутов.

 public class MyAttr: Attribute
{
    public ValueRange ValRange { get; set; }
}
  

Затем я пытаюсь присвоить этот атрибут свойству в соседнем классе:

 public class Foo
{
    [MyAttr(ValRange= new ValueRange())]
    public string Prop { get; set; }
}  
  

Однако компилятор жалуется на следующее:

‘ValRange’ не является допустимым аргументом именованного атрибута, поскольку он не является допустимым типом параметра атрибута

Я также попытался преобразовать класс ValueRange в struct в надежде, что стать типом значения может решить проблему. Есть ли какой-либо способ обойти это?

Ответ №1:

Есть ли какой-либо способ обойти это?

Нет.

Для получения более подробной информации я отсылаю вас к разделу 17.1.3 спецификации C # 4, который я воспроизвожу здесь для вашего удобства:


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

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

Аргумент конструктора или общедоступное поле, которое не имеет ни одного из этих типов, не может использоваться в качестве позиционного или именованного параметра в спецификации атрибута.


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

Комментарии:

1. Я помню, как впервые столкнулся с этим и подумал: «Ого. : (» Но потом я подумал о том, каким был бы мир, если бы я действительно мог это сделать, и понял, что в этом ограничении есть тонна смысла.

2. Я не нашел ничего, подтверждающего это, но я полагал, что это будет так. Спасибо за предоставленный ресурс. Но основано на документации … Я мог бы просто сохранить этот тип Object, а затем использовать его, когда мне нужно использовать его в коде в другом месте (что происходит очень редко). К сожалению, это будет связано с накладными расходами, но разве это было бы невозможно?

3. @Matthew: Нет. Значение, которое вы предоставляете для поля object, должно быть полностью известно во время компиляции либо как константа одного из заданных типов, null, выражение typeof, либо как одномерный массив, где все значения в нем аналогично «известны во время компиляции». Опять же, я повторяю ключевой момент: целью атрибута является добавление информации к метаданным во время компиляции . Компилятор должен иметь полное представление о добавляемой информации. Атрибуты — это не код, который выполняется при запуске программы; это дополнительные метаданные, которые вставляются в скомпилированную программу.

4. ах, я понимаю. Это имеет смысл после переосмысления. Я просто взял класс и превратил его также в атрибут, и этот подход решил проблему.

5. «Я просто взял класс и превратил его также в атрибут, и этот подход решил проблему». — Для некоторого определения «решаемого»?

Ответ №2:

Значения параметров атрибута должны быть разрешимы во время компиляции (т. е. константы).

Смотрите Attribute Parameter Types в MSDN:

Значения, передаваемые атрибутам, должны быть известны компилятору во время компиляции.

Если вы можете создать ValueRange константу, вы можете ее использовать.

Ответ №3:

Есть ли какой-либо способ обойти это?

ДА.

Вы можете заставить свой атрибут использовать Type свойство, а затем использовать типы, которые реализуют определенный интерфейс, для которого код, обрабатывающий этот атрибут, должен был бы предполагать, и, таким образом, также создать неявное, но, надеюсь, документированное требование к своим клиентам:

 public interface IValueRange {
  int Start { get; }
  int End { get; }
}
public class MyAttr : Attribute { 
  // The used type must implement IValueRange
  public Type ValueRangeType { get; set; } 
}

// ....

public class Foo { 

  class FooValueRange : IValueRange {
    public int Start { get { return 10; } }
    public int End { get { return 20; } }
  }
  [MyAttr(ValueRangeType = typeof(FooValueRange))]
  public string Prop { get; set; }

}
  

Это мало чем отличается от многих классов в System.ComponentModel пространстве имен, таких DesignerAttribute как.

Ответ №4:

Параметры атрибута должны быть значениями следующих типов (цитирую статью):

  • Простые типы (bool, byte, char, short, int, long, float и double)
  • строка
  • Система.Тип
  • перечисления
  • объект (Аргументом параметра атрибута типа object должно быть постоянное значение одного из вышеупомянутых типов.)
  • Одномерные массивы любого из вышеупомянутых типов

Редактировать: изменено «константа времени компиляции» на «значение», поскольку типы и массивы не являются константами (спасибо комментатору, который указал на это (и впоследствии по какой-то причине удалил свой комментарий …))

Ответ №5:

Атрибуты могут получать в качестве параметров только константы времени компиляции (например, 3, «hello», typeof(MyClass), «путь к ресурсу, определяющему любые непостоянные данные, которые вам нужны»).

Последний пример (передача типа) Я привел, возможно, поможет вам разработать обходной путь (передать тип, реализующий интерфейс, с помощью нужного вам метода).