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


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

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

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

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

6 452

6 ответов:

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

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

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

EDIT: ответ от Эрика Липперта (перефразированный): нет особой причины, за исключением того, чтобы избежать сложности как в языке, так и в компиляторе для случая использования, который не добавляет большого значения.

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

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

Я не знаю, почему это не разрешено, но это один из возможных обходных путей

[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
{
    ....
}

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

//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
{

}

Это очень хороший вопрос. В моем опыте с атрибутами я думаю, что ограничение существует, потому что при размышлении над атрибутом оно создаст условие, в котором вам нужно будет проверить все возможные перестановки типов: typeof(Validates<string>),typeof(Validates<SomeCustomType>) и т. д...

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

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

мое решение-это что-то вроде этого:

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