ทำไม C # ห้ามประเภทแอตทริบิวต์ทั่วไป?


510

สิ่งนี้ทำให้เกิดข้อยกเว้นเวลาคอมไพล์:

public sealed class ValidatesAttribute<T> : Attribute
{

}

[Validates<string>]
public static class StringValidation
{

}

ฉันรู้ว่า C # ไม่รองรับคุณสมบัติทั่วไป อย่างไรก็ตามหลังจาก Googling มากฉันไม่สามารถหาเหตุผลได้

ไม่มีใครรู้ว่าทำไมประเภททั่วไปไม่สามารถได้มาจากAttributeไหน? ทฤษฎีใด ๆ


17
คุณสามารถทำ [ตรวจสอบ (typeof (สตริง)] - ฉันยอมรับ generics จะดีกว่า ...
ConsultUtah

20
แม้ว่านี้เป็นยังดึกมากคำถามนี้มันเป็นเรื่องน่าเศร้าที่ไม่เพียง แต่คุณลักษณะตัวเอง แต่ยังเรียนแอตทริบิวต์นามธรรม (ซึ่งเห็นได้ชัดไม่สามารถ instantiated เป็นคุณลักษณะ anyways) จะไม่ allwed เช่นนี้abstract class Base<T>: Attribute {}ซึ่งอาจจะใช้ในการสร้างที่ไม่ใช่ คลาสทั่วไปที่ได้รับเช่นนี้:class Concrete: Base<MyType> {}
Lucero

88
ฉันกระหายคุณสมบัติทั่วไปและคุณลักษณะที่ยอมรับ lambdas ลองนึกภาพสิ่งต่าง ๆ เช่น[DependsOnProperty<Foo>(f => f.Bar)]หรือ[ForeignKey<Foo>(f => f.IdBar)]...
Jacek Gorgoń

3
นี่จะเป็นประโยชน์อย่างยิ่งในสถานการณ์ที่ฉันเพิ่งพบ มันจะดีในการสร้าง LinkedValueAttribute ที่ยอมรับประเภททั่วไปและบังคับประเภทนั้นกับค่าจริงที่ระบุ ฉันสามารถใช้สำหรับ enums เพื่อระบุค่า "เริ่มต้น" ของ enum อื่นที่ควรใช้หากเลือกค่า enum นี้ สามารถระบุแอตทริบิวต์เหล่านี้ได้หลายประเภทสำหรับประเภทที่แตกต่างกันและฉันจะได้รับค่าที่ฉันต้องการตามประเภทที่ฉันต้องการ ฉันสามารถตั้งค่าให้ใช้ Type และ Object แต่การพิมพ์อย่างมากจะเป็นข้อดีอย่างมาก
KeithS

10
หากคุณไม่ทราบ IL เล็ก ๆ น้อย ๆลักษณะนี้มีแนวโน้ม
Jarrod Dixon

คำตอบ:


358

ฉันไม่สามารถตอบได้ว่าทำไมถึงไม่มี แต่ฉันสามารถยืนยันได้ว่ามันไม่ใช่ปัญหา CLI ข้อมูลจำเพาะ CLI ไม่ได้กล่าวถึง (เท่าที่ฉันเห็น) และหากคุณใช้ IL โดยตรงคุณสามารถสร้างแอตทริบิวต์ทั่วไปได้ ส่วนหนึ่งของสเปค C # 3 ที่เรย์แบน - ส่วน 10.1.4 "ข้อมูลจำเพาะของคลาสพื้นฐาน" ไม่ได้ให้เหตุผลใด ๆ

ข้อมูลจำเพาะ ECMA C # 2 ที่มีคำอธิบายประกอบนั้นไม่ได้ให้ข้อมูลที่เป็นประโยชน์ แต่อย่างใดแม้ว่าจะมีตัวอย่างของสิ่งที่ไม่ได้รับอนุญาต

สำเนาข้อกำหนด C # 3 ที่มีหมายเหตุประกอบไว้ของฉันควรมาถึงในวันพรุ่งนี้ ... ฉันจะดูว่าให้ข้อมูลเพิ่มเติมหรือไม่ อย่างไรก็ตามมันเป็นการตัดสินใจทางภาษามากกว่าการใช้งานจริง

แก้ไข: คำตอบจาก Eric Lippert (ถอดความ): ไม่มีเหตุผลเฉพาะยกเว้นเพื่อหลีกเลี่ยงความซับซ้อนในทั้งภาษาและคอมไพเลอร์สำหรับกรณีการใช้งานซึ่งไม่ได้เพิ่มคุณค่ามากนัก


139
"ยกเว้นที่จะหลีกเลี่ยงความซับซ้อนทั้งในภาษาและคอมไพเลอร์" ... และจากคนที่ให้เราร่วมและ contravariance ...
FLQ

254
"ใช้กรณีที่ไม่เพิ่มมูลค่ามาก"? นั่นเป็นความเห็นส่วนตัวซึ่งสามารถให้คุณค่ากับฉันได้มากมาย!
Jon Kruger

34
สิ่งที่รบกวนฉันมากที่สุดเกี่ยวกับการไม่มีคุณสมบัตินี้คือไม่สามารถทำสิ่งต่าง ๆ เช่น [PropertyReference (x => x.SomeProperty)] แต่คุณต้องใช้เวทย์มนตร์และ typeof () ซึ่งฉันคิดว่ามันแย่มาก
Asbjørn Ulsberg

13
@ จอห์น: ฉันคิดว่าคุณประเมินค่าใช้จ่ายในการออกแบบประเมินการใช้และทดสอบคุณสมบัติภาษาใหม่อย่างมากมาย
Jon Skeet

14
ฉันต้องการเพิ่มในการป้องกันของ @ Timwi ว่านี่ไม่ใช่สถานที่เดียวที่พวกเขากำลังพูดถึงและมุมมอง 13K สำหรับคำถามนี้แสดงถึงความสนใจในระดับที่ดีต่อสุขภาพ และขอขอบคุณสำหรับการได้รับคำตอบอย่างเป็นทางการจอน
Jordan Gray

84

แอตทริบิวต์ตกแต่งคลาสที่รวบรวมเวลา แต่คลาสทั่วไปไม่ได้รับข้อมูลประเภทสุดท้ายจนกระทั่งรันไทม์ เนื่องจากแอตทริบิวต์สามารถส่งผลกระทบต่อการรวบรวมจึงต้องเป็น "สมบูรณ์" ในเวลารวบรวม

ดูบทความ MSDN นี้สำหรับข้อมูลเพิ่มเติม


3
บทความระบุว่าไม่สามารถทำได้ แต่ไม่มีเหตุผล ฉันเข้าใจแนวคิดของคำตอบของคุณ คุณรู้เอกสารที่เป็นทางการเพิ่มเติมเกี่ยวกับปัญหานี้หรือไม่?
ไบรอันวัตส์

2
บทความนี้ครอบคลุมถึงข้อเท็จจริงที่ว่า IL ยังคงมีตัวยึดตำแหน่งทั่วไปที่ถูกแทนที่ด้วยชนิดจริงที่รันไทม์ ส่วนที่เหลือถูกอนุมานโดยฉัน ... :)
GalacticCowboy

1
สำหรับสิ่งที่คุ้มค่า VB บังคับใช้ข้อ จำกัด เดียวกัน: "คลาสที่เป็นแบบทั่วไปหรืออยู่ในประเภททั่วไปไม่สามารถสืบทอดจากคลาสของแอตทริบิวต์"
GalacticCowboy

1
ECMA-334, ส่วนที่ 14.16 กล่าวว่า "จำเป็นต้องมีการแสดงออกอย่างต่อเนื่องในบริบทที่ระบุไว้ด้านล่างและสิ่งนี้จะถูกระบุในไวยากรณ์โดยใช้การแสดงออกคงที่ในบริบทเหล่านี้ข้อผิดพลาดเวลาคอมไพล์เกิดขึ้นหากไม่สามารถประเมินนิพจน์ทั้งหมด เวลา." คุณสมบัติอยู่ในรายการ
GalacticCowboy

4
นี่ดูเหมือนจะขัดแย้งกับคำตอบอื่นซึ่งระบุว่า IL จะอนุญาต ( stackoverflow.com/a/294259/3195477 )
UuDdLrLrSs

22

ฉันไม่รู้ว่าทำไมมันถึงไม่ได้รับอนุญาต แต่นี่เป็นวิธีแก้ปัญหาหนึ่งที่เป็นไปได้

[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
น่าเสียดายที่คุณสูญเสียการพิมพ์เวลารวบรวมเมื่อใช้แอตทริบิวต์ ลองนึกภาพแอตทริบิวต์ที่สร้างบางสิ่งบางอย่างของประเภททั่วไป คุณสามารถหลีกเลี่ยงได้ แต่มันจะดี มันเป็นหนึ่งในสิ่งที่ใช้งานง่ายที่คุณประหลาดใจที่คุณไม่สามารถทำได้เช่นความแปรปรวน (ปัจจุบัน)
ไบรอัน Watts

14
น่าเศร้าที่พยายามจะไม่ทำนี่คือเหตุผลที่ฉันพบคำถาม SO นี้ ฉันเดาว่าฉันจะต้องติดกับการจัดการกับ typeof มันรู้สึกเหมือนเป็นคำหลักที่สกปรกตอนนี้หลังจากยาชื่อสามัญมีมานานแล้ว
Chris Marisic

13

นี่ไม่ใช่เรื่องทั่วไปอย่างแท้จริงและคุณยังคงต้องเขียน class attribute เฉพาะต่อประเภท แต่คุณอาจจะสามารถใช้อินเตอร์เฟสพื้นฐานทั่วไปในการเขียนโค้ดแบบป้องกันได้เล็กน้อยเขียนรหัสน้อยกว่าที่ต้องการรับผลประโยชน์จาก polymorphism เป็นต้น

//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เป็นพารามิเตอร์อาจเป็นวิธีที่ดีกว่า


ฉันเห็นด้วยกับคุณ. ฉันมีคำถามนี้มานานและขณะนี้กำลังสร้างระบบการตรวจสอบ ฉันใช้คำศัพท์ปัจจุบันเพื่อถามคำถาม แต่ไม่มีความตั้งใจที่จะใช้วิธีการตามกลไกนี้
ไบรอันวัตส์

ฉันสะดุดในสิ่งนี้ขณะทำงานออกแบบเพื่อเป้าหมายเดียวกัน: การตรวจสอบความถูกต้อง ฉันพยายามทำมันในวิธีที่ง่ายต่อการวิเคราะห์ทั้งสองโดยอัตโนมัติ (เช่นคุณสามารถสร้างรายงานที่อธิบายการตรวจสอบความถูกต้องในแอปพลิเคชันเพื่อยืนยัน) และโดยมนุษย์แสดงรหัส หากไม่ใช่คุณลักษณะฉันไม่แน่ใจว่าทางออกที่ดีที่สุดคืออะไร ... ฉันยังคงลองใช้การออกแบบคุณลักษณะ แต่ประกาศคุณสมบัติเฉพาะประเภทด้วยตนเอง มันใช้งานได้มากกว่าเดิม แต่จุดมุ่งหมายคือเพื่อความน่าเชื่อถือในการรู้กฎการตรวจสอบ (และสามารถรายงานเพื่อยืนยันได้)
bambams

4
คุณสามารถตรวจสอบกับคำจำกัดความประเภททั่วไป (เช่น typeof (ตรวจสอบ <>)) ...
Melvyn

5

นี่คือไม่ได้เป็นคุณสมบัติ # ภาษา C แต่มีการอภิปรายมากอย่างเป็นทางการใน C # repo

จากบันทึกย่อการประชุมบางส่วน :

แม้ว่าสิ่งนี้จะใช้งานได้ตามหลักการ แต่ก็มีข้อบกพร่องในรันไทม์เวอร์ชันส่วนใหญ่เพื่อให้ทำงานได้ไม่ถูกต้อง (ไม่เคยใช้)

เราจำเป็นต้องมีกลไกในการทำความเข้าใจกับรันไทม์เป้าหมายที่ทำงาน เราต้องการสิ่งนั้นในหลาย ๆ สิ่งและกำลังมองหาสิ่งนั้นอยู่ ก่อนหน้านั้นเราไม่สามารถรับมันได้

ผู้สมัครรุ่น 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.