为啥 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&lt;T&gt;: Attribute 可用于创建非泛型派生类,如下所示:class Concrete: Base&lt;MyType&gt; 我渴望通用属性和接受 lambda 的属性。想象一下[DependsOnProperty&lt;Foo&gt;(f =&gt; f.Bar)][ForeignKey&lt;Foo&gt;(f =&gt; 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&lt;string&gt;)typeof(Validates&lt;SomeCustomType&gt;) 等...

在我看来,如果需要根据类型进行自定义验证,则属性可能不是最好的方法。

也许采用SomeCustomValidationDelegateISomeCustomValidator 作为参数的验证类会是更好的方法。

【讨论】:

我同意你的看法。我有这个问题很长时间了,目前正在建立一个验证系统。我用我目前的术语来问这个问题,但无意实施基于这种机制的方法。 我在为同一个目标进行设计时偶然发现了这一点:验证。我正在尝试以一种易于自动分析(即,您可以在应用程序中生成描述验证以进行确认的报告)和人类可视化代码的方式进行分析。如果不是属性,我不确定最好的解决方案是什么......我可能仍会尝试属性设计,但手动声明特定于类型的属性。这需要更多的工作,但目的是为了了解验证规则的可靠性(并能够报告它们以进行确认)。 您可以检查泛型类型定义(即 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# 元组语法的元组类型)。

这些类型不直接在元数据中表示。它们包括描述类型的注释。在所有情况下,您都可以使用底层类型:

objectdynamicIntPtr 而不是 nintunintstring 而不是 string?ValueTuple&lt;int, int&gt; 而不是 (int X, int Y)

来源:https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#generic-attributes

【讨论】:

这非常棒,但它似乎不允许使用参数类型Expression&lt;Func&lt;T, object&gt;&gt;(可能在这里做错了),这意味着不可能做类似的事情:[Lookup&lt;Child&gt;(c =&gt; 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# 禁止泛型属性类型?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能在 C# 应用程序中使用泛型类型作为入口点?

为啥 C# 允许将泛型数组与所有类型的模式进行匹配?

为啥 C# (4.0) 不允许泛型类类型中的协变和逆变?

c# 泛型为啥能解决装箱拆箱问题

C#实体类中如何定义泛型集合类型的属性?

读书笔记 C# Type类型与泛型有关的某些属性浅析