Mengapa C # melarang tipe atribut generik?

510

Ini menyebabkan pengecualian waktu kompilasi:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

Saya menyadari C # tidak mendukung atribut generik. Namun, setelah banyak Googling, saya tidak dapat menemukan alasannya.

Adakah yang tahu mengapa tipe generik tidak dapat diturunkan Attribute? Ada teori?

Bryan Watts
sumber
17
Anda bisa melakukan [Validasi (typeof (string)] - Saya setuju obat generik akan lebih baik ...
ConsultUtah
20
Meskipun ini merupakan tambahan yang sangat terlambat untuk pertanyaan ini, sangat menyedihkan bahwa tidak hanya atribut sendiri tetapi juga kelas atribut abstrak (yang jelas tidak dapat dipakai sebagai atribut anyways) tidak allwed, seperti ini: abstract class Base<T>: Attribute {}yang dapat digunakan untuk membuat non- kelas turunan generik seperti ini:class Concrete: Base<MyType> {}
Lucero
88
Saya menginginkan atribut generik dan atribut yang menerima lambda. Bayangkan hal-hal seperti [DependsOnProperty<Foo>(f => f.Bar)]atau [ForeignKey<Foo>(f => f.IdBar)]...
Jacek Gorgoń
3
Ini akan sangat berguna dalam situasi yang baru saja saya temui; alangkah baiknya untuk membuat LinkedValueAttribute yang menerima tipe generik dan menerapkan tipe itu pada nilai aktual yang ditentukan. Saya bisa menggunakannya untuk enum untuk menentukan nilai "default" dari enum lain yang harus digunakan jika nilai enum ini dipilih. Beberapa atribut ini dapat ditentukan untuk tipe yang berbeda, dan saya bisa mendapatkan nilai yang saya butuhkan berdasarkan tipe yang saya butuhkan. Saya dapat mengaturnya untuk menggunakan Type dan Object tetapi diketik dengan kuat akan menjadi nilai tambah yang besar.
KeithS
10
Jika Anda tidak keberatan sedikit IL, ini terlihat menjanjikan .
Jarrod Dixon

Jawaban:

358

Yah, saya tidak bisa menjawab mengapa itu tidak tersedia, tetapi saya dapat mengkonfirmasi bahwa ini bukan masalah CLI. Spesifikasi CLI tidak menyebutkannya (sejauh yang saya bisa lihat) dan jika Anda menggunakan IL secara langsung, Anda dapat membuat atribut generik. Bagian dari spesifikasi C # 3 yang melarangnya - bagian 10.1.4 "Spesifikasi dasar kelas" tidak memberikan justifikasi apa pun.

Spesifikasi ECMA C # 2 yang dianotasi juga tidak memberikan informasi bermanfaat, meskipun itu memberikan contoh tentang apa yang tidak diperbolehkan.

Salinan spec C # 3 saya yang beranotasi akan tiba besok ... Saya akan melihat apakah itu memberikan informasi lebih lanjut. Bagaimanapun, ini jelas merupakan keputusan bahasa daripada yang runtime.

EDIT: Jawaban dari Eric Lippert (diparafrasekan): tidak ada alasan khusus, kecuali untuk menghindari kompleksitas dalam bahasa dan kompiler untuk kasus penggunaan yang tidak menambah banyak nilai.

Jon Skeet
sumber
139
"kecuali untuk menghindari kerumitan dalam bahasa dan kompiler" ... dan itu dari orang-orang yang memberi kita co dan contravariance ...
flq
254
"use case yang tidak menambah banyak nilai"? Itu pendapat subjektif, itu bisa memberi saya banyak nilai!
Jon Kruger
34
Hal yang paling mengganggu saya adalah tidak memiliki fitur ini, tidak dapat melakukan hal-hal seperti [PropertyReference (x => x.SomeProperty)]. Sebagai gantinya, Anda memerlukan string dan typeof (), yang menurut saya agak menyebalkan.
Asbjørn Ulsberg
13
@ John: Saya pikir Anda sangat meremehkan biaya merancang, menentukan, mengimplementasikan dan menguji fitur bahasa baru.
Jon Skeet
14
Saya hanya ingin menambahkan pertahanan @ Timwi bahwa ini bukan satu-satunya tempat yang sedang dibahas , dan pandangan 13K tentang pertanyaan ini menyiratkan tingkat minat yang sehat. Juga: terima kasih telah mendapatkan jawaban resmi, Jon.
Jordan Grey
84

Atribut mendekorasi kelas pada waktu kompilasi, tetapi kelas generik tidak menerima informasi tipe terakhirnya sampai runtime. Karena atribut dapat mempengaruhi kompilasi, itu harus "selesai" pada waktu kompilasi.

Lihat artikel MSDN ini untuk informasi lebih lanjut.

GalacticCowboy
sumber
3
Artikel itu menyatakan kembali bahwa itu tidak mungkin, tetapi tanpa alasan. Saya secara konseptual memahami jawaban Anda. Apakah Anda tahu ada dokumentasi resmi tentang masalah ini?
Bryan Watts
2
Artikel tersebut tidak mencakup fakta bahwa IL masih berisi placeholder generik yang diganti dengan tipe aktual saat runtime. Sisanya disimpulkan oleh saya ... :)
GalacticCowboy
1
Untuk apa nilainya, VB memberlakukan batasan yang sama: "Kelas yang generik atau terkandung dalam tipe generik tidak dapat mewarisi dari kelas atribut."
GalacticCowboy
1
ECMA-334, bagian 14.16 mengatakan "Ekspresi konstan diperlukan dalam konteks yang tercantum di bawah ini dan ini ditunjukkan dalam tata bahasa dengan menggunakan ekspresi konstan. Dalam konteks ini, kesalahan waktu kompilasi terjadi jika ekspresi tidak dapat sepenuhnya dievaluasi pada saat dikompilasi- waktu." Atribut ada dalam daftar.
GalacticCowboy
4
Ini tampaknya bertentangan dengan jawaban lain yang menyatakan bahwa IL akan mengizinkannya. ( stackoverflow.com/a/294259/3195477 )
UuDdLrLrSs
22

Saya tidak tahu mengapa itu tidak diizinkan, tetapi ini adalah salah satu solusi yang mungkin

[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
{
    ....
}
GeekyMonkey
sumber
3
Sayangnya, Anda kehilangan mengetik waktu kompilasi saat mengonsumsi atribut. Bayangkan atribut menciptakan sesuatu dari tipe generik. Anda dapat mengatasinya, tetapi itu akan menyenangkan; itu salah satu hal intuitif yang Anda tidak bisa lakukan, seperti varians (saat ini).
Bryan Watts
14
Sedihnya mencoba untuk tidak melakukan ini adalah mengapa saya menemukan pertanyaan SO ini. Saya kira saya harus tetap berurusan dengan typeof. Ini benar-benar terasa seperti kata kunci kotor sekarang setelah obat generik sudah ada sejak lama.
Chris Marisic
13

Ini bukan benar-benar generik dan Anda masih harus menulis kelas atribut spesifik per jenis, tetapi Anda mungkin dapat menggunakan antarmuka basis generik untuk mengkodekan sedikit defensif, menulis kode lebih sedikit daripada yang diperlukan, mendapatkan manfaat polimorfisme dll.

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

}
nawfal
sumber
8

Ini pertanyaan yang sangat bagus. Dalam pengalaman saya dengan atribut, saya pikir kendala di tempat karena ketika merefleksikan atribut itu akan menciptakan suatu kondisi di mana Anda harus memeriksa semua jenis kemungkinan permutasi: typeof(Validates<string>), typeof(Validates<SomeCustomType>), dll ...

Menurut pendapat saya, jika validasi khusus diperlukan tergantung pada jenisnya, atribut mungkin bukan pendekatan terbaik.

Mungkin kelas validasi yang menggunakan a SomeCustomValidationDelegateatau a ISomeCustomValidatorsebagai parameter akan menjadi pendekatan yang lebih baik.

Ichiban
sumber
Saya setuju dengan kamu. Saya sudah memiliki pertanyaan ini sejak lama, dan saat ini saya sedang membangun sistem validasi. Saya menggunakan terminologi saya saat ini untuk mengajukan pertanyaan, tetapi tidak memiliki niat menerapkan pendekatan berdasarkan mekanisme ini.
Bryan Watts
Saya menemukan ini saat mengerjakan desain untuk tujuan yang sama: validasi. Saya mencoba melakukannya dengan cara yang mudah untuk menganalisis keduanya secara otomatis (yaitu, Anda dapat menghasilkan laporan yang menggambarkan validasi dalam aplikasi untuk konfirmasi) dan oleh manusia yang memvisualisasikan kode. Jika bukan atribut, saya tidak yakin solusi apa yang terbaik ... Saya mungkin masih mencoba desain atribut, tetapi secara manual mendeklarasikan atribut tipe-spesifik. Ini sedikit lebih banyak pekerjaan, tetapi tujuannya adalah untuk keandalan mengetahui aturan validasi (dan dapat melaporkannya untuk konfirmasi).
bambams
4
Anda dapat memeriksa terhadap definisi tipe umum (yaitu typeof (Validasi <>)) ...
Melvyn
5

Ini bukan fitur bahasa C # saat ini, namun ada banyak diskusi tentang repo bahasa C # resmi .

Dari beberapa catatan rapat :

Meskipun ini pada prinsipnya akan berfungsi, ada bug di sebagian besar versi runtime sehingga tidak akan berfungsi dengan benar (tidak pernah dilakukan).

Kami membutuhkan mekanisme untuk memahami target runtime yang berfungsi. Kami membutuhkan itu untuk banyak hal, dan saat ini sedang melihat itu. Sampai saat itu, kita tidak bisa menerimanya.

Calon untuk versi C # utama, jika kita dapat membuat cukup banyak versi runtime yang berurusan dengannya.

Owen Pauling
sumber
1

Solusi saya adalah seperti ini:

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; }
Razon
sumber