Почему C# запрещает универсальные типы атрибутов?
Это вызывает исключение во время компиляции:
public sealed class ValidatesAttribute<T> : Attribute
{
}
[Validates<string>]
public static class StringValidation
{
}
Я понимаю, что C# не поддерживает общие атрибуты. Однако, после долгих поисков, я не могу найти причину.
Кто-нибудь знает, почему родовые типы не могут быть получены из Attribute
? Есть теории?
10 ответов
Ну, я не могу ответить, почему это не доступно, но я могу подтвердить, что это не проблема CLI. Спецификация CLI не упоминает об этом (насколько я вижу), и если вы используете IL напрямую, вы можете создать общий атрибут. Часть спецификации C# 3, которая ее запрещает - раздел 10.1.4 "Базовая спецификация класса" не дает никакого оправдания.
Аннотированная спецификация ECMA C# 2 также не дает никакой полезной информации, хотя она предоставляет пример того, что недопустимо.
Моя копия аннотированной спецификации C# 3 должна прибыть завтра... Я посмотрю, даст ли это больше информации. В любом случае, это определенно решение на языке, а не на этапе исполнения.
РЕДАКТИРОВАТЬ: Ответ от Эрика Липперта (перефразировано): нет конкретной причины, за исключением того, чтобы избежать сложности как в языке, так и в компиляторе для варианта использования, который не добавляет особой ценности.
Атрибут украшает класс во время компиляции, но универсальный класс не получает свою окончательную информацию о типе до времени выполнения. Поскольку атрибут может влиять на компиляцию, он должен быть "полным" во время компиляции.
См. Эту статью 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
{
}
были добавлены в C# 10 в качестве функции предварительного просмотра , что означает, что вы должны установить для &lt;LangVersion&gt; значение Preview , чтобы включить эту функцию; И функция может измениться перед ее окончательным выпуском.
Обратите внимание, что у этой функции есть некоторые ограничения, такие как:
Вы можете применить полностью закрытый универсальный атрибут. Другими словами, все параметры типа должны быть указаны. Например, не допускается следующее:
public class GenericType<T> { [GenericAttribute<T>()] // Not allowed! generic attributes must be fully closed types. public string Method() => default; }
а также
Аргументы типа должны удовлетворять тем же ограничениям, что и оператор typeof. Типы, для которых требуются аннотации метаданных, не допускаются. Примеры включают следующее:
- ,
nuint
- (или любой ссылочный тип, допускающий значение NULL)
- (или любые другие типы кортежей, использующие синтаксис кортежа C#).
Эти типы не представлены напрямую в метаданных. Они включают аннотации, описывающие тип. Во всех случаях вместо этого можно использовать базовый тип:
object
заdynamic
.IntPtr
вместоnint
или жеunint
.string
вместоstring?
.ValueTuple<int, int>
вместо(int X, int Y)
.
Это очень хороший вопрос. В моем опыте с атрибутами, я думаю, что ограничение имеет место, потому что при отражении на атрибуте это создаст условие, при котором вам придется проверять все возможные перестановки типов: typeof(Validates<string>)
, typeof(Validates<SomeCustomType>)
, так далее...
На мой взгляд, если в зависимости от типа требуется настраиваемая проверка, атрибут может быть не лучшим подходом.
Возможно, класс проверки, который принимает SomeCustomValidationDelegate
или ISomeCustomValidator
в качестве параметра будет лучшим подходом.
В настоящее время это не особенность языка C#, однако существует много дискуссий по поводу официального репозитория языка C#.
Несмотря на то, что это работает в принципе, в большинстве версий среды выполнения есть ошибки, так что это не будет работать правильно (это никогда не выполнялось).
Нам нужен механизм, чтобы понять, на какой целевой среде он работает. Нам это нужно для многих вещей, и сейчас мы на это смотрим. До тех пор мы не можем принять это.
Кандидат на основную версию C#, если мы можем заставить достаточное количество версий времени выполнения справиться с этим.
Мой обходной путь примерно такой:
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; }
Я не знаю, почему это запрещено, но это возможное решение:
[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
{
....
}