C # ข้อ จำกัด ประเภททั่วไปสำหรับทุกสิ่งที่เป็นโมฆะ


111

ดังนั้นฉันจึงมีคลาสนี้:

public class Foo<T> where T : ???
{
    private T item;

    public bool IsNull()
    {
        return item == null;
    }

}

ตอนนี้ฉันกำลังมองหาข้อ จำกัด nullประเภทที่ช่วยให้ผมใช้ทุกอย่างที่เป็นพารามิเตอร์ชนิดที่สามารถ นั่นหมายถึงประเภทการอ้างอิงทั้งหมดรวมถึงประเภทNullable( T?) ทั้งหมด:

Foo<String> ... = ...
Foo<int?> ... = ...

ควรจะเป็นไปได้

การใช้classเป็นข้อ จำกัด ประเภททำให้ฉันใช้ประเภทอ้างอิงเท่านั้น

ข้อมูลเพิ่มเติม: ฉันกำลังเขียนแอปพลิเคชั่นไปป์และฟิลเตอร์และต้องการใช้nullข้อมูลอ้างอิงเป็นรายการสุดท้ายที่ผ่านเข้าไปในไปป์ไลน์เพื่อให้ตัวกรองทุกตัวสามารถปิดได้อย่างสวยงามทำการล้างข้อมูล ฯลฯ ...


1
@Tim ที่ไม่อนุญาตสำหรับ Nullables
Rik

ลิงค์นี้อาจช่วยคุณได้: social.msdn.microsoft.com/Forums/en-US/…
Réda Mattar

2
ไม่สามารถดำเนินการได้โดยตรง บางทีคุณสามารถบอกเราเพิ่มเติมเกี่ยวกับสถานการณ์ของคุณได้ หรือบางทีคุณอาจใช้IFoo<T>เป็นประเภทการทำงานและสร้างอินสแตนซ์ผ่านวิธีการของโรงงาน? ที่สามารถนำไปใช้งานได้
จอน

ฉันไม่แน่ใจว่าทำไมคุณถึงต้องการหรือจำเป็นต้อง จำกัด บางสิ่งด้วยวิธีนี้ หากเจตนาเพียงอย่างเดียวของคุณคือเปลี่ยน "if x == null" เป็น if x.IsNull () "สิ่งนี้ดูเหมือนจะไม่มีจุดหมายและไม่ตรงกับความต้องการของนักพัฒนา 99.99% ที่คุ้นเคยกับไวยากรณ์เดิมคอมไพเลอร์จะไม่ยอมให้คุณทำ" ถ้า (int) x == null "อย่างไรก็ตามคุณได้รับการคุ้มครองแล้ว
RJ Lohan

1
นี่เป็นเรื่องที่พูดถึงกันอย่างแพร่หลายใน SO stackoverflow.com/questions/209160/…และstackoverflow.com/questions/13794554/…
Maxim Gershkovich

คำตอบ:


22

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

ฉันตระหนักดีว่าการตรวจสอบรันไทม์เท่านั้นอาจไม่สามารถยอมรับได้ แต่ในกรณี:

public class Foo<T>
{
    private T item;

    public Foo()
    {
        var type = typeof(T);

        if (Nullable.GetUnderlyingType(type) != null)
            return;

        if (type.IsClass)
            return;

        throw new InvalidOperationException("Type is not nullable or reference type.");
    }

    public bool IsNull()
    {
        return item == null;
    }
}

จากนั้นโค้ดต่อไปนี้จะคอมไพล์ แต่โค้ดสุดท้าย ( foo3) แสดงข้อยกเว้นในตัวสร้าง:

var foo1 = new Foo<int?>();
Console.WriteLine(foo1.IsNull());

var foo2 = new Foo<string>();
Console.WriteLine(foo2.IsNull());

var foo3= new Foo<int>();  // THROWS
Console.WriteLine(foo3.IsNull());

31
หากคุณกำลังจะทำสิ่งนี้ตรวจสอบให้แน่ใจว่าคุณได้ทำการตรวจสอบในตัวสร้างแบบคงที่มิฉะนั้นคุณจะชะลอการสร้างอินสแตนซ์ของคลาสทั่วไปของคุณทุกครั้ง (โดยไม่จำเป็น)
Eamon Nerbonne

2
@EamonNerbonne คุณไม่ควรเพิ่มข้อยกเว้นจากตัวสร้างแบบคงที่: msdn.microsoft.com/en-us/library/bb386039.aspx
Matthew Watson

5
หลักเกณฑ์ไม่สมบูรณ์ หากคุณต้องการตรวจสอบนี้คุณจะต้องแลกกับต้นทุนของการตรวจสอบรันไทม์เทียบกับความไม่สะดวกของข้อยกเว้นในตัวสร้างแบบคงที่ เนื่องจากคุณกำลังติดตั้งเครื่องวิเคราะห์แบบคงที่สำหรับคนยากจนที่นี่จึงไม่ควรทิ้งข้อยกเว้นนี้ยกเว้นระหว่างการพัฒนา สุดท้ายนี้แม้ว่าคุณจะต้องการหลีกเลี่ยงข้อยกเว้นการก่อสร้างแบบคงที่โดยเสียค่าใช้จ่ายทั้งหมด (ไม่ฉลาด) คุณก็ยังควรทำงานให้มากที่สุดเท่าที่จะเป็นไปได้ในตัวสร้างอินสแตนซ์เช่นโดยการตั้งค่าสถานะ "isBorked" หรืออะไรก็ตาม
Eamon Nerbonne

อนึ่งฉันไม่คิดว่าคุณควรพยายามทำสิ่งนี้เลย ในสถานการณ์ส่วนใหญ่ฉันต้องการยอมรับสิ่งนี้เป็นข้อ จำกัด C # มากกว่าที่จะพยายามทำงานกับนามธรรมที่รั่วไหลและล้มเหลวได้ง่าย เช่นวิธีแก้ปัญหาที่แตกต่างกันอาจเป็นเพียงแค่ต้องการคลาสหรือต้องการโครงสร้าง (และทำให้ em เป็นโมฆะอย่างชัดเจน) - หรือทำทั้งสองอย่างและมีสองเวอร์ชัน นั่นไม่ใช่คำวิจารณ์ของโซลูชันนี้ เป็นเพียงว่าปัญหานี้ไม่สามารถแก้ไขได้ดีเว้นแต่คุณจะเต็มใจที่จะเขียนเครื่องวิเคราะห์ดอกโรสลินแบบกำหนดเอง
Eamon Nerbonne

1
คุณจะได้รับสิ่งที่ดีที่สุดจากทั้งสองโลก - เก็บstatic bool isValidTypeฟิลด์ที่คุณตั้งไว้ในตัวสร้างแบบคงที่จากนั้นตรวจสอบแฟล็กนั้นในตัวสร้างอินสแตนซ์และโยนว่าเป็นประเภทที่ไม่ถูกต้องหรือไม่ดังนั้นคุณจะไม่ทำการตรวจสอบทั้งหมดทุกครั้งที่สร้าง อินสแตนซ์ ฉันใช้รูปแบบนี้บ่อยๆ
Mike Marynowski

20

ฉันไม่รู้วิธีใช้งานเทียบเท่ากับORในชื่อสามัญ อย่างไรก็ตามฉันสามารถเสนอให้ใช้คำสำคัญเริ่มต้นเพื่อสร้าง null สำหรับประเภทที่ว่างเปล่าและค่า 0 สำหรับโครงสร้าง:

public class Foo<T>
{
    private T item;

    public bool IsNullOrDefault()
    {
        return Equals(item, default(T));
    }
}

คุณยังสามารถใช้เวอร์ชัน Nullable ของคุณได้:

class MyNullable<T> where T : struct
{
    public T Value { get; set; }

    public static implicit operator T(MyNullable<T> value)
    {
        return value != null ? value.Value : default(T);
    }

    public static implicit operator MyNullable<T>(T value)
    {
        return new MyNullable<T> { Value = value };
    }
}

class Foo<T> where T : class
{
    public T Item { get; set; }

    public bool IsNull()
    {
        return Item == null;
    }
}

ตัวอย่าง:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(new Foo<MyNullable<int>>().IsNull()); // true
        Console.WriteLine(new Foo<MyNullable<int>> {Item = 3}.IsNull()); // false
        Console.WriteLine(new Foo<object>().IsNull()); // true
        Console.WriteLine(new Foo<object> {Item = new object()}.IsNull()); // false

        var foo5 = new Foo<MyNullable<int>>();
        int integer = foo5.Item;
        Console.WriteLine(integer); // 0

        var foo6 = new Foo<MyNullable<double>>();
        double real = foo6.Item;
        Console.WriteLine(real); // 0

        var foo7 = new Foo<MyNullable<double>>();
        foo7.Item = null;
        Console.WriteLine(foo7.Item); // 0
        Console.WriteLine(foo7.IsNull()); // true
        foo7.Item = 3.5;
        Console.WriteLine(foo7.Item); // 3.5
        Console.WriteLine(foo7.IsNull()); // false

        // var foo5 = new Foo<int>(); // Not compile
    }
}

Nullable <T> ดั้งเดิมในเฟรมเวิร์กคือโครงสร้างไม่ใช่คลาส ฉันไม่คิดว่าควรสร้าง Wrapper ประเภทอ้างอิงที่จะเลียนแบบประเภทค่า
Niall Connaughton

1
คำแนะนำแรกโดยใช้ค่าเริ่มต้นนั้นสมบูรณ์แบบ! ตอนนี้แม่แบบของฉันที่มีการส่งคืนประเภททั่วไปสามารถคืนค่าว่างสำหรับวัตถุและค่าเริ่มต้นสำหรับชนิดที่มีอยู่แล้วภายใน
Casey Anderson

13

ฉันพบปัญหานี้ในกรณีที่ง่ายกว่าในการต้องการวิธีการคงที่ทั่วไปที่สามารถใช้อะไรก็ได้ "nullable" (ไม่ว่าจะเป็นประเภทอ้างอิงหรือ Nullables) ซึ่งทำให้ฉันมาถึงคำถามนี้โดยไม่มีวิธีแก้ปัญหาที่น่าพอใจ ดังนั้นฉันจึงคิดวิธีแก้ปัญหาของตัวเองซึ่งค่อนข้างง่ายในการแก้ปัญหามากกว่าคำถามที่ระบุไว้ของ OP โดยมีเพียงสองวิธีที่มากเกินไปวิธีหนึ่งที่ใช้ a Tและมีข้อ จำกัดwhere T : classและอีกวิธีหนึ่งที่ใช้T?และมีwhere T : structและมีความ

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

    //this class is to avoid having to supply generic type arguments 
    //to the static factory call (see CA1000)
    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return Foo<TFoo>.Create(value);
        }

        public static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return Foo<TFoo?>.Create(value);
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo(T value)
        {
            item = value;
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>(TFoo value)
            where TFoo : class
        {
            return new Foo<TFoo>(value);
        }

        internal static Foo<TFoo?> Create<TFoo>(TFoo? value)
            where TFoo : struct
        {
            return new Foo<TFoo?>(value);
        }
    }

ตอนนี้เราสามารถใช้มันได้ดังนี้:

        var foo1 = new Foo<int>(1); //does not compile
        var foo2 = Foo.Create(2); //does not compile
        var foo3 = Foo.Create(""); //compiles
        var foo4 = Foo.Create(new object()); //compiles
        var foo5 = Foo.Create((int?)5); //compiles

หากคุณต้องการตัวสร้างแบบไม่มีพารามิเตอร์คุณจะไม่ได้รับความสวยงามจากการโอเวอร์โหลด แต่คุณยังสามารถทำสิ่งนี้ได้:

    public static class Foo
    {
        public static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return Foo<TFoo>.Create<TFoo>();
        }

        public static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return Foo<TFoo?>.CreateNullable<TFoo>();
        }
    }

    public class Foo<T>
    {
        private T item;

        private Foo()
        {
        }

        public bool IsNull()
        {
            return item == null;
        }

        internal static Foo<TFoo> Create<TFoo>()
            where TFoo : class
        {
            return new Foo<TFoo>();
        }

        internal static Foo<TFoo?> CreateNullable<TFoo>()
            where TFoo : struct
        {
            return new Foo<TFoo?>();
        }
    }

และใช้มันดังนี้:

        var foo1 = new Foo<int>(); //does not compile
        var foo2 = Foo.Create<int>(); //does not compile
        var foo3 = Foo.Create<string>(); //compiles
        var foo4 = Foo.Create<object>(); //compiles
        var foo5 = Foo.CreateNullable<int>(); //compiles

วิธีแก้ปัญหานี้มีข้อเสียเล็กน้อยอย่างหนึ่งคือคุณอาจต้องการใช้ 'ใหม่' ในการสร้างวัตถุ ก็คือว่าคุณจะไม่สามารถที่จะใช้Foo<T>เป็นอาร์กิวเมนต์ประเภททั่วไปสำหรับข้อ จำกัด where TFoo: new()ประเภทของสิ่งที่ชอบ: สุดท้ายคือบิตของรหัสพิเศษที่คุณต้องการที่นี่ซึ่งจะเพิ่มขึ้นโดยเฉพาะอย่างยิ่งหากคุณต้องการตัวสร้างที่โอเวอร์โหลดหลายตัว


8

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

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

public class SomeClass<T>
{
    public SomeClass()
    {
        // JIT-compile time check, so it doesn't even have to evaluate.
        if (default(T) != null)
            throw new InvalidOperationException("SomeClass<T> requires T to be a nullable type.");

        T variable;
        // This still won't compile
        // variable = null;
        // but because you know it's a nullable type, this works just fine
        variable = default(T);
    }
}

3

ข้อ จำกัด ประเภทดังกล่าวเป็นไปไม่ได้ ตามเอกสารของข้อ จำกัด ประเภทไม่มีข้อ จำกัด ที่รวบรวมทั้งประเภทที่เป็นโมฆะและประเภทอ้างอิง เนื่องจากข้อ จำกัด สามารถรวมเข้าด้วยกันได้เท่านั้นจึงไม่มีวิธีใดที่จะสร้างข้อ จำกัด ดังกล่าวโดยการรวมกันได้

อย่างไรก็ตามคุณสามารถถอยกลับไปใช้พารามิเตอร์ประเภท unconstraint ได้เนื่องจากคุณสามารถตรวจสอบ == null ได้ตลอดเวลา หากประเภทเป็นชนิดค่าการตรวจสอบจะประเมินเป็นเท็จเสมอ จากนั้นคุณอาจได้รับคำเตือน R # "Possible Compare of value type with null" ซึ่งไม่สำคัญตราบใดที่ความหมายนั้นเหมาะกับคุณ

อีกทางเลือกหนึ่งคือการใช้

object.Equals(value, default(T))

แทนการตรวจสอบค่าว่างเนื่องจากค่าเริ่มต้น (T) โดยที่ T: class เป็นโมฆะเสมอ อย่างไรก็ตามนี่หมายความว่าคุณไม่สามารถแยกแยะสภาพอากาศได้เนื่องจากค่าที่ไม่เป็นโมฆะไม่เคยถูกตั้งค่าอย่างชัดเจนหรือเพียงแค่ตั้งค่าเป็นค่าเริ่มต้น


ฉันคิดว่าปัญหาคือจะตรวจสอบค่าที่ไม่เคยตั้งค่าได้อย่างไร ต่างจาก null ดูเหมือนจะชี้ว่าค่าเริ่มต้นแล้ว
Ryszard Dżegan

วิธีนี้ไม่ทำให้แนวทางเป็นโมฆะเนื่องจากประเภทค่าจะถูกตั้งค่าไว้เสมอ (อย่างน้อยก็โดยปริยายเป็นค่าเริ่มต้นตามลำดับ)
Sven Amann


-2
    public class Foo<T>
    {
        private T item;

        public Foo(T item)
        {
            this.item = item;
        }

        public bool IsNull()
        {
            return object.Equals(item, null);
        }
    }

    var fooStruct = new Foo<int?>(3);
        var b = fooStruct.IsNull();

        var fooStruct1 = new Foo<int>(3);
        b = fooStruct1.IsNull();

        var fooStruct2 = new Foo<int?>(null);
        b = fooStruct2.IsNull();

        var fooStruct3 = new Foo<string>("qqq");
        b = fooStruct3.IsNull();

        var fooStruct4 = new Foo<string>(null);
        b = fooStruct4.IsNull();

การพิมพ์นี้ช่วยให้ Foo <int> (42) และ IsNull () ใหม่จะส่งคืนเท็จซึ่งในขณะที่ความหมายถูกต้องไม่มีความหมายโดยเฉพาะ
RJ Lohan

1
42 คือ "คำตอบสำหรับคำถามสูงสุดของชีวิตจักรวาลและทุกสิ่ง" ใส่เพียง: IsNull สำหรับทุกค่า int จะส่งกลับเท็จ (แม้จะเป็นค่า 0)
Ryszard Dżegan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.