C#がジェネリック属性タイプを禁止するのはなぜですか?


510

これにより、コンパイル時の例外が発生します。

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

C#は一般的な属性をサポートしていないことに気づきました。しかし、グーグルで何度も調べた結果、理由がわかりません。

ジェネリック型から派生できない理由を誰かが知っていますAttributeか?理論は?


17
あなたは[Validates(typeof(string)]-ジェネリックスがもっと良いことに同意します...
ConsultUtah

20
これは、この質問には非常に遅れて追加があるにもかかわらず、それだけではなく、このように、自分自身だけでなく、allwedされていません(もちろん、とにかく属性としてインスタンス化することはできません)抽象属性クラス属性という悲しい:abstract class Base<T>: Attribute {}非作成するために使用することができました次のようなジェネリック派生クラス:class Concrete: Base<MyType> {}
Lucero

88
私は一般的な属性とラムダを受け入れる属性を切望しています。以下のようなものを想像し[DependsOnProperty<Foo>(f => f.Bar)]たり[ForeignKey<Foo>(f => f.IdBar)]...
ヤツェクゴルゴン

3
これは、今遭遇した状況で非常に役立ちます。ジェネリック型を受け入れ、指定された実際の値にその型を適用するLinkedValueAttributeを作成すると便利です。これを列挙型に使用して、この列挙型の値が選択された場合に使用する必要がある別の列挙型の「デフォルト」値を指定できます。これらの属性の複数を異なるタイプに指定でき、必要なタイプに基づいて必要な値を取得できます。TypeとObjectを使用するように設定できますが、強く型付けされると非常に便利です。
KeithS 2012

10
少しILを気にしなければ、これは有望に見えます。
ジャロッドディクソン

回答:


358

まあ、それが利用できない理由には答えられませんが、CLIの問題ではないことを確認できます。CLI仕様では(私が知る限り)言及されておらず、ILを直接使用する場合は、汎用属性を作成できます。それを禁止するC#3仕様の一部-セクション10.1.4「クラスベースの仕様」では、正当化されていません。

注釈付きECMA C#2仕様も、許可されていないものの例を提供していますが、有用な情報を提供していません。

注釈付きのC#3仕様のコピーが明日届くはずです...詳細がわかるかどうか確認します。とにかく、それはランタイムの決定というよりは間違いなく言語の決定です。

編集:Eric Lippertからの回答(言い換え):特別な理由はありませんが、あまり価値のないユースケースで言語とコンパイラの両方が複雑になるのを避けるためです。


139
「言語とコンパイラーの両方で複雑さを回避することを除いて」...そしてそれは私たちに共
矛盾と矛盾

254
「あまり価値のないユースケース」?それは主観的な意見です、それは私に多くの価値を与える可能性があります!
ジョンクルーガー

34
この機能がないことで最も気になるのは、[PropertyReference(x => x.SomeProperty)]のようなことを実行できないことです。代わりに、マジックストリングとtypeof()が必要です。
アスビョルンUlsberg

13
@ジョン:新しい言語機能の設計、指定、実装、テストのコストを過小評価していると思います。
Jon Skeet

14
@Timwiの弁護を付け加えたいのは、議論さているのはここだけではないということです。この質問に対する13Kの見解は、健全なレベルの関心を意味しています。また、信頼できる回答を得てくれてありがとう、ジョン。
ジョーダングレイ

84

属性はコンパイル時にクラスを装飾しますが、ジェネリッククラスは実行時まで最終的な型情報を受け取りません。この属性はコンパイルに影響を与える可能性があるため、コンパイル時に「完全」である必要があります。

詳細については、このMSDNの記事参照してください。


3
記事では、それらは不可能ではあるが理由なしに述べられている。私はあなたの答えを概念的に理解しています。この問題に関する公式のドキュメントをもう知っていますか?
ブライアンワッツ

2
この記事では、ILには実行時に実際の型で置き換えられる汎用プレースホルダーがまだ含まれているという事実が含まれています。残りは私によって推測されました... :)
GalacticCowboy

1
それだけの価値があるため、VBは同じ制約を適用します。「ジェネリックであるクラスまたはジェネリック型に含まれるクラスは、属性クラスから継承できません。」
GalacticCowboy

1
ECMA-334、セクション14.16は、「定数式は以下にリストされたコンテキストで必要であり、これは定数式を使用して文法で示されます。これらのコンテキストでは、式がコンパイル時に完全に評価できない場合、コンパイル時エラーが発生します。時間。" 属性はリストにあります。
GalacticCowboy

4
これは、ILが許可するという別の回答とは矛盾しているようです。(stackoverflow.com/a/294259/3195477
UuDdLrLrSs

22

なぜ許可されないのかはわかりませんが、これは考えられる回避策の1つです

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

3
残念ながら、属性を使用すると、コンパイル時のタイピングが失われます。属性がジェネリック型の何かを作成すると想像してください。あなたはそれを回避することができますが、それは素晴らしいでしょう。これは、分散(現在)のように、実行できないと驚いた直感的なものの1つです。
ブライアンワッツ

14
悲しいことにこれを行わないようにしようと思ったのが、このSOの質問を見つけた理由です。typeofの扱いにこだわる必要があると思います。ジェネリックが長く存在するようになった今、それは本当に汚いキーワードだと感じています。
Chris Marisic、2009

13

これは本当に汎用的ではなく、タイプごとに特定の属性クラスを作成する必要がありますが、汎用的な基本インターフェースを使用して、少し防御的にコーディングしたり、他の方法で必要とされるよりも少ないコードを作成したり、ポリモーフィズムの利点を得ることができます。

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

}

8

これは非常に良い質問です。私の属性の経験では、属性が反映されると、可能なすべての型の置換typeof(Validates<string>)typeof(Validates<SomeCustomType>)、など)をチェックする必要がある条件が作成されるため、制約が設定されていると思います。

私の意見では、タイプに応じてカスタム検証が必要な場合、属性は最善のアプローチではない可能性があります。

おそらく、SomeCustomValidationDelegateまたはをISomeCustomValidatorパラメータとして受け取る検証クラスがより良いアプローチでしょう。


仰るとおりです。長い間この質問があり、現在検証システムを構築しています。私は現在の用語を使って質問しましたが、このメカニズムに基づくアプローチを実装するつもりはありません。
ブライアンワッツ

同じ目標である検証の設計に取り組んでいるときに、これに遭遇しました。自動的に(つまり、確認のためにアプリケーションで検証を説明するレポートを生成できます)と人間がコードを視覚化することの両方で簡単に分析できるようにしています。属性でない場合、私は最善の解決策が何であるかわかりません...私はまだ属性設計を試すかもしれませんが、手動で型固有の属性を宣言します。これは少し手間がかかりますが、その目的は、検証ルールを知ることの信頼性(および確認のためにそれらについてレポートできること)です。
バンバム

4
ジェネリック型定義(typeof(Validates <>)など)に対してチェックできます...
Melvyn

5

これは現在C#言語の機能ではありませんが、公式のC#言語リポジトリについては多くの議論があります。

会議のメモから:

これは原理的には機能しますが、ランタイムのほとんどのバージョンにはバグがあるため、正しく機能しません(実行されなかった)。

動作するターゲットランタイムを理解するメカニズムが必要です。私たちは多くのことにそれを必要とし、現在それを検討しています。それまで、私たちはそれを取ることができません。

主要なC#バージョンの候補(十分な数のランタイムバージョンを処理できる場合)。


1

私の回避策は次のようなものです:

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; }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.