Чому C # забороняє загальні типи атрибутів?


510

Це спричиняє виняток із часу компіляції:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

Я розумію, що C # не підтримує загальних атрибутів. Однак після довгого гуглінгу я не можу знайти причину.

Хтось знає, чому родові типи не можуть бути похідними Attribute? Будь-які теорії?


17
Ви можете зробити [Validates (typeof (string)]) - Я згоден, що дженерики будуть приємнішими ...
ConsultUtah

20
Незважаючи на те, що це дуже пізнє доповнення до цього питання, сумно, що не тільки самі атрибути, але й абстрактні класи атрибутів (які, очевидно, не можна інстанціювати як атрибути), не охоплені таким чином, як це: abstract class Base<T>: Attribute {}що можна було б використовувати для створення родові похідні класи на кшталт цього:class Concrete: Base<MyType> {}
Lucero

88
Я жадаю загальних ознак і атрибутів, що приймають лямбда. Уявіть такі речі, як [DependsOnProperty<Foo>(f => f.Bar)]або [ForeignKey<Foo>(f => f.IdBar)]...
Jacek Gorgoń

3
Це було б надзвичайно корисно в ситуації, з якою я щойно стикався; було б непогано створити LinkedValueAttribute, який прийняв загальний тип і застосував цей тип за вказаним фактичним значенням. Я міг би використовувати його для перерахунків, щоб вказати значення "за замовчуванням" іншого перерахунку, який слід використовувати, якщо вибрано це значення перерахування. Кілька цих атрибутів можна вказати для різних типів, і я можу отримати потрібне мені значення, виходячи з потрібного мені типу. Я можу налаштувати його на використання типу та об’єкта, але сильно набраний був би величезним плюсом.
KeithS

10
Якщо ви не заперечуєте проти невеликого ІЛ, це виглядає багатообіцяюче .
Jarrod Dixon

Відповіді:


358

Ну, я не можу відповісти, чому це недоступно, але можу підтвердити, що це не проблема CLI. Специфікація CLI не згадує про це (наскільки я бачу), і якщо ви використовуєте IL безпосередньо, ви можете створити загальний атрибут. Частина специфікації C # 3, яка забороняє її, - розділ 10.1.4 "Специфікація базового класу" не дає жодних обґрунтувань.

Позначена специфікація ECMA C # 2 також не дає корисної інформації, хоча вона є прикладом того, що заборонено.

Моя копія поміченої специфікації C # 3 повинна надійти завтра ... Я побачу, чи це дає більше інформації. У будь-якому випадку, це, безумовно, мовне рішення, а не час виконання.

EDIT: Відповідь Еріка Ліпперта (перефразовано): немає особливих причин, за винятком уникнення складності як у мові, так і в компіляторі для випадку використання, який не додає великої цінності.


139
"окрім уникнення складності як у мові, так і в компіляторі" ... і це від людей, що надають нам
спільність та протиріччя

254
"використовувати регістр, який не додає великої вартості"? Це суб'єктивна думка, це могло б дати мені велику цінність!
Джон Крюгер

34
Те, що мене найбільше турбує про відсутність цієї функції, - це не в змозі робити такі речі, як [PropertyReference (x => x.SomeProperty)]. Натомість вам потрібні чарівні струни та typeof (), які, на мою думку, відстійні.
Asbjørn Ulsberg

13
@John: Я думаю, ви значно недооцінюєте витрати на розробку, конкретизацію, впровадження та тестування нової мовної функції.
Джон Скіт

14
Я просто хочу додати в захист @ Timwi, що це не єдине місце, про яке вони обговорюються , і 13К поглядів на це питання передбачає здоровий рівень зацікавленості. Також: дякую за отримання авторитетної відповіді, Джон.
Джордан Сірий

84

Атрибут прикрашає клас під час компіляції, але загальний клас не отримує остаточну інформацію про тип до часу виконання. Оскільки атрибут може впливати на компіляцію, він повинен бути "завершеним" під час компіляції.

Дивіться цю статтю MSDN для отримання додаткової інформації.


3
У статті повторюється, що вони неможливі, але без причини. Я концептуально розумію вашу відповідь. Чи знаєте ви якісь офіційні документи з цього питання?
Брайан Воттс

2
У статті висвітлюється той факт, що ІР все ще містить загальні заповнювачі, які заміщені фактичним типом під час виконання. Решта мені було зроблено висновок ... :)
GalacticCowboy

1
Для чого це варто, VB застосовує те саме обмеження: "Класи, що є загальними або містяться в загальному типі, не можуть успадковувати клас атрибутів."
GalacticCowboy

1
У розділі 14.16 ECMA-334 сказано: "Постійні вирази потрібні у перелічених нижче контекстах, і це вказується в граматиці за допомогою постійного вираження. У цих контекстах виникає помилка часу компіляції, якщо вираз не можна повністю оцінити при компіляції- час ». Атрибути є у списку.
GalacticCowboy

4
Це, здається, суперечить іншій відповіді, яка говорить, що ІЛ дозволить це. ( Stackoverflow.com/a/294259/3195477 )
UuDdLrLrSs

22

Я не знаю, чому це не дозволено, але це один із можливих шляхів вирішення

[AttributeUsage(AttributeTargets.Class)]
public class ClassDescriptionAttribute : Attribute
{
    public ClassDescriptionAttribute(Type KeyDataType)
    {
        _KeyDataType = KeyDataType;
    }

    public Type KeyDataType
    {
        get { return _KeyDataType; }
    }
    private Type _KeyDataType;
}


[ClassDescriptionAttribute(typeof(string))]
class Program
{
    ....
}

3
На жаль, під час споживання атрибуту ви втрачаєте введення компіляційного часу. Уявіть, що атрибут створює щось загального типу. Можна обійтися навколо цього, але було б непогано; це одна з тих інтуїтивно зрозумілих речей, яких ти не дивуєшся, як, наприклад, дисперсія (зараз).
Брайан Уоттс

14
На жаль, намагаюся цього не робити, тому я знайшов це питання. Я думаю, що мені доведеться просто дотримуватися справи з typeof. Це справді відчуває себе брудним ключовим словом зараз після того, як дженерики існують так давно.
Кріс Марісіч

13

Це не є справді загальним, і вам все одно доведеться писати конкретний клас атрибутів для кожного типу, але ви, можливо, зможете використовувати загальний базовий інтерфейс, щоб кодувати трохи оборонно, писати менший код, ніж потрібно, отримати переваги поліморфізму тощо.

//an interface which means it can't have its own implementation. 
//You might need to use extension methods on this interface for that.
public interface ValidatesAttribute<T>
{
    T Value { get; } //or whatever that is
    bool IsValid { get; } //etc
}

public class ValidatesStringAttribute : Attribute, ValidatesAttribute<string>
{
    //...
}
public class ValidatesIntAttribute : Attribute, ValidatesAttribute<int>
{
    //...
}

[ValidatesString]
public static class StringValidation
{

}
[ValidatesInt]
public static class IntValidation
{

}

8

Це дуже гарне запитання. З мого досвіду , з атрибутами, я думаю , що обмеження на місці , тому що при відображенні від атрибута було б створити умови , в яких ви повинні перевірити всі можливі перестановки типу: typeof(Validates<string>), typeof(Validates<SomeCustomType>), і т.д. ...

На мою думку, якщо потрібна спеціальна перевірка залежно від типу, атрибут може виявитися не найкращим підходом.

Можливо, кращим підходом буде клас перевірки, який приймає параметр a SomeCustomValidationDelegateабо ISomeCustomValidatora.


Я погоджуюсь з тобою. У мене це питання було давно, і наразі будую систему перевірки. Я використовував свою сучасну термінологію, щоб задати питання, але не маю наміру реалізувати підхід, заснований на цьому механізмі.
Брайан Уоттс

Я наткнувся на це, працюючи над дизайном для тієї самої мети: валідація. Я намагаюсь зробити це таким чином, що легко проаналізувати як автоматично (тобто ви можете створити звіт, що описує перевірку в додатку для підтвердження), так і людиною візуалізуючи код. Якщо не атрибути, я не впевнений, що було б найкращим рішенням ... Я все-таки можу спробувати дизайн атрибутів, але вручну оголошувати атрибути типу. Це трохи більше роботи, але мета - надійність знання правил перевірки (і можливість звітувати про них для підтвердження).
бамбуки

4
ви можете перевірити визначення загального типу (тобто typeof (Validates <>)) ...
Мелвін

5

Наразі це не є мовою C #, проте є багато обговорень на офіційному репо-мові C # .

З деяких записок про зустрічі :

Незважаючи на те, що це працює в принципі, є помилки в більшості версій програми, щоб він не працював правильно (він ніколи не виконувався).

Нам потрібен механізм, щоб зрозуміти, на якому цільовому режимі він працює. Це нам потрібно для багатьох речей, і зараз це дивимось. До цього ми не можемо прийняти це.

Кандидат у головну версію C #, якщо ми зможемо зробити достатню кількість версій виконання, щоб це вирішити.


1

Мій спосіб вирішення приблизно такий:

public class DistinctType1IdValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type1> validator;

    public DistinctIdValidation()
    {
        validator = new DistinctValidator<Type1>(x=>x.Id);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

public class DistinctType2NameValidation : ValidationAttribute
{
    private readonly DistinctValidator<Type2> validator;

    public DistinctType2NameValidation()
    {
        validator = new DistinctValidator<Type2>(x=>x.Name);
    }

    public override bool IsValid(object value)
    {
        return validator.IsValid(value);
    }
}

...
[DataMember, DistinctType1IdValidation ]
public Type1[] Items { get; set; }

[DataMember, DistinctType2NameValidation ]
public Type2[] Items { get; set; }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.