Почему С# запрещает общие типы атрибутов?

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

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

Я понимаю, что С# не поддерживает общие атрибуты. Тем не менее, после большого Googling, я не могу найти причину.

Кто-нибудь знает, почему общие типы не могут быть получены из Attribute? Любые теории?

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

}

Ответ 5

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

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

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

Ответ 6

Мое обходное решение выглядит примерно так:

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