为啥 C# 禁止泛型属性类型?
Posted
技术标签:
【中文标题】为啥 C# 禁止泛型属性类型?【英文标题】:Why does C# forbid generic attribute types?为什么 C# 禁止泛型属性类型? 【发布时间】:2010-09-22 14:15:09 【问题描述】:这会导致编译时异常:
public sealed class ValidatesAttribute<T> : Attribute
[Validates<string>]
public static class StringValidation
我意识到 C# 不支持通用属性。但是,经过多次谷歌搜索,我似乎找不到原因。
有谁知道为什么泛型类型不能从Attribute
派生?有什么理论吗?
【问题讨论】:
你可以做 [Validates(typeof(string)] - 我同意泛型会更好...... 尽管这是对这个问题的一个非常晚的补充,但令人遗憾的是,不仅属性本身而且抽象属性类(显然不能被实例化为属性)都不允许,如下所示:abstract class Base<T>: Attribute
可用于创建非泛型派生类,如下所示:class Concrete: Base<MyType>
我渴望通用属性和接受 lambda 的属性。想象一下[DependsOnProperty<Foo>(f => f.Bar)]
或[ForeignKey<Foo>(f => f.IdBar)]
...
这在我刚刚遇到的情况下非常有用;最好创建一个接受泛型类型的 LinkedValueAttribute 并在指定的实际值上强制执行该类型。我可以将它用于枚举来指定另一个枚举的“默认”值,如果选择了这个枚举值,则应该使用该值。可以为不同的类型指定多个这些属性,我可以根据我需要的类型得到我需要的值。我可以将其设置为使用类型和对象,但强类型化将是一个巨大的优势。
如果你不介意一点 IL,this looks promising。
【参考方案1】:
好吧,我无法回答为什么它不可用,但我可以确认这不是 CLI 问题。 CLI 规范没有提到它(据我所知),如果你直接使用 IL,你可以创建一个通用属性。 C# 3 规范中禁止它的部分 - 第 10.1.4 节“类基础规范”没有给出任何理由。
带注释的 ECMA C# 2 规范也没有提供任何有用的信息,尽管它提供了一个不允许的示例。
我的带注释的 C# 3 规范副本将于明天到达...我会看看是否提供更多信息。无论如何,这绝对是语言决定而不是运行时决定。
编辑:Eric Lippert 的回答(释义):没有特别的原因,除了避免语言和编译器的复杂性,因为用例不会增加太多价值。
【讨论】:
“除了避免语言和编译器的复杂性”......以及来自给我们协变和逆变的人...... “没有增加太多价值的用例”?这是一个主观意见,它可以为我提供很多价值! 没有这个功能最让我困扰的事情是不能做像 [PropertyReference(x => x.SomeProperty)] 这样的事情。相反,您需要魔术字符串和 typeof(),我认为这很糟糕。 @John:我认为您大大低估了设计、指定、实施和测试新语言功能的成本。 我只是想在@Timwi 的辩护中补充一点,这并不是they are being discussed 唯一的地方,关于这个问题的 13K 浏览量意味着健康水平的兴趣。另外:感谢乔恩得到权威的回答。【参考方案2】:一个属性在编译时修饰一个类,但是一个泛型类直到运行时才收到它的最终类型信息。由于该属性会影响编译,因此在编译时它必须是“完整的”。
请参阅此MSDN article 了解更多信息。
【讨论】:
文章重申它们是不可能的,但没有理由。我从概念上理解你的答案。你知道关于这个问题的更多官方文档吗? 这篇文章确实涵盖了这样一个事实,即 IL 仍然包含在运行时用实际类型替换的通用占位符。其余的都是我推断的...... :) 不管怎样,VB 强制执行相同的约束:“泛型或包含在泛型类型中的类不能从属性类继承。” ECMA-334,第 14.16 节说“在下面列出的上下文中需要常量表达式,这在语法中通过使用常量表达式来指示。在这些上下文中,如果无法在编译时完全评估表达式。”属性在列表中。 这似乎与另一个答案相矛盾,该答案指出 IL 将允许它。 (***.com/a/294259/3195477)【参考方案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
....
【讨论】:
不幸的是,您在使用该属性时会丢失编译时类型。想象一下该属性创建了一些泛型类型的东西。您可以解决它,但这会很好;这是您惊讶地无法做到的直观事情之一,例如方差(当前)。 可悲的是试图不这样做是我发现这个 SO 问题的原因。我想我只需要坚持处理 typeof。在泛型已经存在这么久之后,现在感觉就像一个肮脏的关键字。 这没有回答问题。 正如 GeekyMonkey 所说 - 这是一种解决方法。这比等待 C# 10 更好 - 所以谢谢你【参考方案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
作为参数的验证类会是更好的方法。
【讨论】:
我同意你的看法。我有这个问题很长时间了,目前正在建立一个验证系统。我用我目前的术语来问这个问题,但无意实施基于这种机制的方法。 我在为同一个目标进行设计时偶然发现了这一点:验证。我正在尝试以一种易于自动分析(即,您可以在应用程序中生成描述验证以进行确认的报告)和人类可视化代码的方式进行分析。如果不是属性,我不确定最好的解决方案是什么......我可能仍会尝试属性设计,但手动声明特定于类型的属性。这需要更多的工作,但目的是为了了解验证规则的可靠性(并能够报告它们以进行确认)。 您可以检查泛型类型定义(即 typeof(Validates))...【参考方案6】:目前这不是 C# 语言功能,但 there is much discussion on the official C# language repo。
来自some meeting notes:
尽管这在原则上可行,但大多数情况下都存在错误 运行时的版本,使其无法正常工作(它是 从未锻炼过)。
我们需要一种机制来了解它适用于哪个目标运行时。我们 很多事情都需要它,目前正在研究它。直到 那我们就受不了了。
主要 C# 版本的候选人,如果我们可以提供足够的数量 的运行时版本处理它。
【讨论】:
【参考方案7】:Generic Attributes 已作为 preview 功能添加到 C# 10,这意味着您必须设置 <LangVersion> to Preview 才能启用此功能;并且该功能可能会在最终发布之前发生变化。
请注意,该功能有一些限制,例如:
您可以应用完全封闭的构造通用属性。换句话说,必须指定所有类型参数。例如,以下是不允许的:
public class GenericType<T> [GenericAttribute<T>()] // Not allowed! generic attributes must be fully closed types. public string Method() => default;
和
类型参数必须满足与 typeof 运算符相同的限制。不允许使用需要元数据注释的类型。示例包括:
dynamic
nint
,nuint
string?
(或任何可为空的引用类型)(int X, int Y)
(或任何其他使用 C# 元组语法的元组类型)。这些类型不直接在元数据中表示。它们包括描述类型的注释。在所有情况下,您都可以使用底层类型:
object
为dynamic
。IntPtr
而不是nint
或unint
。string
而不是string?
。ValueTuple<int, int>
而不是(int X, int Y)
。
来源:https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#generic-attributes
【讨论】:
这非常棒,但它似乎不允许使用参数类型Expression<Func<T, object>>
(可能在这里做错了),这意味着不可能做类似的事情:[Lookup<Child>(c => c.ParentId)]
【参考方案8】:
我的解决方法是这样的:
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;
【讨论】:
以上是关于为啥 C# 禁止泛型属性类型?的主要内容,如果未能解决你的问题,请参考以下文章