【问题标题】: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

标签: c# generics .net-attributes


【解决方案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 将允许它。 (stackoverflow.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# 元组语法的元组类型)。

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

      • objectdynamic
      • IntPtr 而不是 nintunint
      • string 而不是 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; }
      

      【讨论】:

        猜你喜欢
        • 2012-01-27
        • 2017-12-31
        • 2016-05-10
        • 2020-08-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-08
        • 1970-01-01
        相关资源
        最近更新 更多