Почему C # запрещает универсальные типы атрибутов?

510

Это вызывает исключение во время компиляции:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

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

Кто-нибудь знает, почему родовые типы не могут быть получены из Attribute? Есть теории?

Брайан Уоттс
источник
17
Вы могли бы сделать [Проверяет (typeof (строка)) - я согласен, что дженерики были бы более хорошими ...
ConsultUtah
20
Несмотря на то, что это очень позднее добавление к этому вопросу, печально, что не только сами атрибуты, но и абстрактные классы атрибутов (которые, очевидно, не могут быть созданы как атрибуты в любом случае) не объединены, как это: abstract class Base<T>: Attribute {}это может быть использовано для создания общие производные классы, такие как:class Concrete: Base<MyType> {}
Lucero
88
Я жажду общих атрибутов и атрибутов, принимающих лямбды. Вообразите вещи как [DependsOnProperty<Foo>(f => f.Bar)]или [ForeignKey<Foo>(f => f.IdBar)]...
Яцек Горгонь
3
Это было бы чрезвычайно полезно в ситуации, с которой я только что столкнулся; было бы неплохо создать LinkedValueAttribute, который принимает универсальный тип и применяет этот тип к указанному фактическому значению. Я мог бы использовать его для перечислений, чтобы указать значение «по умолчанию» другого перечисления, которое следует использовать, если выбрано это значение перечисления. Множество этих атрибутов может быть указано для разных типов, и я могу получить нужное мне значение на основе нужного мне типа. Я могу настроить его на использование Типа и Объекта, но строгая типизация будет огромным плюсом.
KeithS
10
Если вы не возражаете против небольшого IL, это выглядит многообещающе .
Джаррод Диксон

Ответы:

358

Ну, я не могу ответить, почему это не доступно, но я могу подтвердить, что это не проблема CLI. Спецификация CLI не упоминает об этом (насколько я вижу), и если вы используете IL напрямую, вы можете создать общий атрибут. Часть спецификации C # 3, которая запрещает это - раздел 10.1.4 «Базовая спецификация класса» не дает никакого оправдания.

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

Моя копия аннотированной спецификации C # 3 должна прибыть завтра ... Я посмотрю, даст ли это больше информации. В любом случае, это определенно решение на языке, а не на этапе исполнения.

РЕДАКТИРОВАТЬ: Ответ от Эрика Липперта (перефразировано): нет особой причины, кроме как избежать сложности как в языке, так и в компиляторе для варианта использования, который не добавляет особой ценности.

Джон Скит
источник
139
«за исключением того, чтобы избежать сложности как в языке, так и в компиляторе» ... и это от людей, которые дают нам совместную и противоречивую информацию ...
FLQ
254
"вариант использования, который не добавляет особой ценности"? Это субъективное мнение, оно может дать мне большую ценность!
Джон Крюгер
34
Больше всего меня беспокоит отсутствие этой функции - невозможность делать что-то вроде [PropertyReference (x => x.SomeProperty)]. Вместо этого вам нужны магические строки и typeof (), которые, я думаю, отстой.
Асбьерн Ульсберг
13
@John: я думаю, что вы сильно недооцениваете стоимость разработки, определения, реализации и тестирования новой языковой функции.
Джон Скит
14
Я просто хочу добавить в защиту @Timwi, что это не единственное место, где они обсуждаются , и взгляды 13K на этот вопрос подразумевают здоровый уровень интереса. Также: спасибо за авторитетный ответ, Джон.
Джордан Грей,
84

Атрибут украшает класс во время компиляции, но универсальный класс не получает свою окончательную информацию о типе до времени выполнения. Поскольку атрибут может влиять на компиляцию, он должен быть «завершен» во время компиляции.

Посмотрите эту статью MSDN для получения дополнительной информации.

GalacticCowboy
источник
3
В статье повторяется, что они невозможны, но без причины. Я концептуально понимаю ваш ответ. Знаете ли вы какие-либо официальные документы по этому вопросу?
Брайан Уоттс
2
В статье рассматривается тот факт, что IL по-прежнему содержит общие заполнители, которые заменяются фактическим типом во время выполнения. Остальное было выведено мной ... :)
GalacticCowboy
1
Что бы ни стоило, VB применяет то же ограничение: «Классы, которые являются универсальными или содержатся в универсальном типе, не могут наследовать от класса атрибута».
GalacticCowboy
1
ECMA-334, раздел 14.16, говорит, что «константные выражения требуются в контекстах, перечисленных ниже, и это указано в грамматике с помощью константы-выражения. В этих контекстах возникает ошибка времени компиляции, если выражение не может быть полностью оценено при компиляции». время." Атрибуты есть в списке.
GalacticCowboy
4
Это, кажется, противоречит другому ответу, который заявляет, что IL позволит это. ( 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
{
    ....
}
GeekyMonkey
источник
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>), и т.д. ...

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

Возможно, класс валидации, который принимает SomeCustomValidationDelegateили ISomeCustomValidatorв качестве параметра, будет лучшим подходом.

Ichiban
источник
Я с тобой согласен. У меня был этот вопрос в течение долгого времени, и в настоящее время я создаю систему проверки. Я использовал свою текущую терминологию, чтобы задать вопрос, но не собираюсь реализовывать подход, основанный на этом механизме.
Брайан Уоттс
Я наткнулся на это, работая над проектом с той же целью: проверка. Я пытаюсь сделать это способом, который легко анализировать как автоматически (т. Е. Вы можете создать отчет, описывающий проверку в приложении для подтверждения), так и человеком, визуализирующим код. Если бы не атрибуты, я не уверен, что будет лучшим решением ... Я все еще мог бы попробовать дизайн атрибутов, но вручную объявить атрибуты, специфичные для типа. Это немного больше работы, но цель заключается в надежности знания правил валидации (и возможности сообщать о них для подтверждения).
Бамбамс
4
Вы можете проверить соответствие определений универсального типа (то есть typeof (Validates <>)) ...
Melvyn
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; }
Razon
источник