การเปรียบเทียบค่า Null หรือค่าดีฟอลต์ของอาร์กิวเมนต์ทั่วไปใน C #


288

ฉันมีวิธีการทั่วไปที่กำหนดไว้เช่นนี้:

public void MyMethod<T>(T myArgument)

สิ่งแรกที่ฉันต้องการทำคือตรวจสอบว่าค่าของ myArgument เป็นค่าเริ่มต้นสำหรับประเภทนั้นหรือไม่ดังนี้:

if (myArgument == default(T))

แต่นี่ไม่ได้รวบรวมเพราะฉันไม่ได้รับประกันว่า T จะใช้ตัวดำเนินการ == ดังนั้นฉันจึงเปลี่ยนรหัสเป็น:

if (myArgument.Equals(default(T)))

ตอนนี้การคอมไพล์ แต่จะล้มเหลวหาก myArgument เป็นโมฆะซึ่งเป็นส่วนหนึ่งของสิ่งที่ฉันกำลังทดสอบ ฉันสามารถเพิ่มการตรวจสอบ null อย่างชัดเจนเช่นนี้:

if (myArgument == null || myArgument.Equals(default(T)))

ตอนนี้ฉันรู้สึกซ้ำซ้อน ReSharper ยังแนะนำให้ฉันเปลี่ยน myArgument == ส่วนที่ว่างเป็น myArgument == default (T) ซึ่งเป็นที่ที่ฉันเริ่ม มีวิธีที่ดีกว่าในการแก้ปัญหานี้หรือไม่?

ฉันต้องการสนับสนุนทั้งประเภทการอ้างอิงและประเภทค่า


ตอนนี้ C # รองรับผู้ประกอบการแบบมีเงื่อนไข Nullซึ่งเป็นน้ำตาล syntatic สำหรับตัวอย่างสุดท้ายที่คุณให้ if (myArgument?.Equals( default(T) ) != null )รหัสของคุณจะกลายเป็น
wizard07KSU

1
@ wizard07KSU นั่นใช้ไม่ได้กับประเภทค่าเช่นประเมินtrueในทุกกรณีเพราะEqualsจะถูกเรียกใช้สำหรับประเภทค่าเสมอเนื่องจากmyArgumentไม่สามารถnullอยู่ในกรณีนี้และผลลัพธ์ของEquals(บูลีน) จะไม่เป็นnullเช่นนั้น
jasper

มีค่าเกือบเท่ากัน (ดังนั้นไม่ต้องลงคะแนนให้ปิด): ไม่สามารถใช้โอเปอเรเตอร์ == กับประเภททั่วไปใน C # ได้หรือไม่
GSerg

คำตอบ:


585

EqualityComparer<T>.Defaultเพื่อหลีกเลี่ยงมวยวิธีที่ดีที่สุดเพื่อเปรียบเทียบข้อมูลทั่วไปเพื่อความเท่าเทียมกันอยู่กับ ประการนี้IEquatable<T>(โดยไม่ต้องชกมวย) เช่นเดียวกับobject.EqualsและจัดการกับNullable<T>ความแตกต่าง "ยก" ทั้งหมด ดังนั้น:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

สิ่งนี้จะตรงกับ:

  • null สำหรับคลาส
  • null (ว่าง) สำหรับ Nullable<T>
  • ศูนย์ / เท็จ / ฯลฯ สำหรับ structs อื่น ๆ

29
ว้าวช่างคลุมเครือด้วยความยินดี! นี่เป็นวิธีที่จะไปแน่นอนว่ารุ่งโรจน์
Nick Farina

1
คำตอบที่ดีที่สุดแน่นอน ไม่มีเส้นหยักในรหัสของฉันหลังจากเขียนใหม่เพื่อใช้โซลูชันนี้
นาธานริดลีย์

14
คำตอบที่ดี! ยิ่งไปกว่านั้นคือการเพิ่มวิธีขยายสำหรับบรรทัดของรหัสนี้เพื่อให้คุณสามารถไป obj.IsDefaultForType ()
rikoe

2
@nawfal ในกรณีของPerson, p1.Equals(p2)จะขึ้นอยู่กับว่าจะดำเนินการIEquatable<Person>เกี่ยวกับ API ของสาธารณะหรือผ่านการดำเนินงานอย่างชัดเจน - คือสามารถคอมไพเลอร์เห็นสาธารณะEquals(Person other)วิธี อย่างไรก็ตาม; ในยาชื่อสามัญ , IL เดียวกันจะใช้สำหรับทุกคนT; สิ่งT1ที่เกิดขึ้นกับการใช้งานIEquatable<T1>จำเป็นต้องได้รับการปฏิบัติเหมือนกันกับสิ่งT2ที่ไม่ได้ทำดังนั้นไม่มันจะไม่มองเห็นEquals(T1 other)วิธีการแม้ว่ามันจะมีอยู่ที่รันไทม์ก็ตาม ในทั้งสองกรณียังมีnullการคิด (วัตถุทั้งสอง) ดังนั้นด้วยยาชื่อสามัญฉันจะใช้รหัสที่ฉันโพสต์
Marc Gravell

5
ฉันไม่สามารถตัดสินใจได้ว่าคำตอบนี้ผลักฉันออกไปจากหรือใกล้ชิดกับความบ้า +1
Steven Liekens

118

เกี่ยวกับสิ่งนี้:

if (object.Equals(myArgument, default(T)))
{
    //...
}

การใช้static object.Equals()วิธีนี้ช่วยให้คุณไม่ต้องnullตรวจสอบด้วยตัวเอง object.อาจไม่จำเป็นต้องมีคุณสมบัติที่เหมาะสมสำหรับการโทรขึ้นอยู่กับบริบทของคุณ แต่โดยปกติแล้วฉันจะใช้คำนำหน้าstaticด้วยชื่อประเภทเพื่อทำให้รหัสละลายได้มากขึ้น


2
คุณยังสามารถวาง "วัตถุ" ส่วนหนึ่งเพราะมันซ้ำซ้อน if (เท่ากับ (myArgument, default (T)))
Stefan Moser

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

8
จำเป็นที่จะต้องทราบว่ามันจะทำให้เกิดการชกมวยและในบางกรณีมันอาจมีความสำคัญ
nightcoder

2
สำหรับฉันมันไม่ทำงานเมื่อใช้จำนวนเต็มที่บรรจุอยู่ในกล่องแล้ว เพราะมันจะเป็นวัตถุและค่าเริ่มต้นสำหรับวัตถุนั้นเป็นโมฆะแทน 0
riezebosch

28

ฉันสามารถค้นหาบทความ Microsoft Connectที่กล่าวถึงปัญหานี้ในรายละเอียดบางอย่าง:

น่าเสียดายที่พฤติกรรมนี้เกิดจากการออกแบบและไม่มีวิธีแก้ปัญหาที่ง่ายในการเปิดใช้งานการใช้กับพารามิเตอร์ประเภทที่อาจมีประเภทของค่า

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

public class Test<T> where T : Exception

หากทราบว่าเป็นประเภทประเภทค่าให้ดำเนินการทดสอบความเท่าเทียมกันของค่าเฉพาะตามประเภทที่แน่นอนที่ใช้ ไม่มีการเปรียบเทียบ "เริ่มต้น" ที่ดีที่นี่เนื่องจากการเปรียบเทียบการอ้างอิงไม่มีความหมายกับประเภทค่าและคอมไพเลอร์ไม่สามารถรู้ได้ว่าการเปรียบเทียบค่าเฉพาะกับการเปล่ง คอมไพเลอร์สามารถส่งการเรียกไปยัง ValueType.Equals (Object) แต่วิธีนี้ใช้การสะท้อนและไม่มีประสิทธิภาพค่อนข้างเมื่อเทียบกับการเปรียบเทียบค่าเฉพาะ ดังนั้นแม้ว่าคุณจะต้องระบุข้อ จำกัด ประเภทค่าบน T ไม่มีอะไรที่สมเหตุสมผลสำหรับคอมไพเลอร์ที่จะสร้างที่นี่:

public class Test<T> where T : struct

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

นี่คือสิ่งที่คุณสามารถทำได้ ...

ฉันได้ตรวจสอบแล้วว่าวิธีการทั้งสองวิธีนี้ใช้สำหรับการเปรียบเทียบการอ้างอิงและประเภทค่าทั่วไป:

object.Equals(param, default(T))

หรือ

EqualityComparer<T>.Default.Equals(param, default(T))

ในการเปรียบเทียบกับตัวดำเนินการ "==" คุณจะต้องใช้วิธีใดวิธีหนึ่งต่อไปนี้:

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

public void MyMethod<T>(T myArgument) where T : MyBase

คอมไพเลอร์ตระหนักถึงวิธีการดำเนินการ MyBaseและจะไม่โยน "Operator '==' ไม่สามารถใช้กับตัวถูกดำเนินการประเภท 'T' และ 'T'" ข้อผิดพลาดที่คุณเห็นตอนนี้

อีกตัวเลือกหนึ่งที่จะ จำกัด T ชนิดใด ๆ IComparableที่นำไปปฏิบัติ

public void MyMethod<T>(T myArgument) where T : IComparable

แล้วใช้CompareToวิธีการที่กำหนดโดยอินเตอร์เฟซ IComparable


4
"พฤติกรรมนี้เกิดจากการออกแบบและไม่มีวิธีแก้ปัญหาที่ง่ายในการเปิดใช้งานการใช้กับพารามิเตอร์ประเภทที่อาจมีประเภทค่า" ที่จริง Microsoft ผิด มีวิธีแก้ไขปัญหาง่าย ๆ : MS ควรขยาย ceq opcode เพื่อทำงานกับประเภทของค่าเป็นตัวดำเนินการระดับบิต จากนั้นพวกเขาสามารถให้ข้อมูลพื้นฐานที่ใช้ opcode นี้เช่น object.BitwiseOrReferenceEquals <T> (ค่า, ค่าเริ่มต้น (T)) ที่ใช้ ceq สำหรับค่าและการอ้างอิงทั้งสองชนิดนี้จะตรวจสอบเพื่อความเท่าเทียมกันค่าที่เหมาะสมของค่า ( แต่ประเภทอ้างอิงอ้างอิงบิตความเสมอภาคเป็นเช่นเดียวกับ object.ReferenceEquals)
Qwertie

1
ฉันคิดว่าลิงก์ Microsoft Connect ที่คุณต้องการคือconnect.microsoft.com/VisualStudio/feedback/details/304501//
Qwertie

18

ลองสิ่งนี้:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

ที่ควรรวบรวมและทำสิ่งที่คุณต้องการ


<code> ค่าเริ่มต้น (T) </code> ซ้ำซ้อนใช่หรือไม่ <code> EqualityComparer <T> .Default.Equals (myArgument) </code> ควรใช้เล่ห์เหลี่ยม
Joshcodes

2
1) คุณลองแล้วและ 2) คุณเปรียบเทียบอะไรกับวัตถุที่เปรียบเทียบ EqualsวิธีการIEqualityComparerจะใช้เวลาสองข้อโต้แย้งวัตถุทั้งสองเพื่อเปรียบเทียบจึงไม่มีมันเป็นไปไม่ซ้ำซ้อน
Lasse V. Karlsen

นี่คือคำตอบที่ดียิ่งขึ้นกว่า IMHO เพราะมันจัดการมวย / unboxing และประเภทอื่น ๆ ดูคำตอบของคำถาม "ปิดเป็นดัก": stackoverflow.com/a/864860/210780
ashes999

7

(แก้ไข)

Marc Gravell มีคำตอบที่ดีที่สุด แต่ฉันต้องการโพสต์โค้ดขนาดเล็กที่ฉันทำงานเพื่อแสดงให้เห็น เพียงแค่เรียกใช้สิ่งนี้ในแอปคอนโซล C # ง่ายๆ:

public static class TypeHelper<T>
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer<T>.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper<int>.IsDefault(1)); //False
    Console.WriteLine(TypeHelper<int>.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper<string>.IsDefault("test")); //False
    Console.WriteLine(TypeHelper<string>.IsDefault(null)); //True //True

    Console.ReadKey();
}

อีกอย่างหนึ่ง: คนที่มี VS2008 ลองใช้วิธีนี้เป็นวิธีเสริมหรือไม่ ฉันติดกับปี 2005 ที่นี่และฉันอยากรู้ว่าจะอนุญาตหรือไม่


แก้ไข:นี่คือวิธีทำให้มันทำงานเป็นวิธีส่วนขยาย:

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // value type
        Console.WriteLine(1.IsDefault());
        Console.WriteLine(0.IsDefault());

        // reference type
        Console.WriteLine("test".IsDefault());
        // null must be cast to a type
        Console.WriteLine(((String)null).IsDefault());
    }
}

// The type cannot be generic
public static class TypeHelper
{
    // I made the method generic instead
    public static bool IsDefault<T>(this T val)
    {
        return EqualityComparer<T>.Default.Equals(val, default(T));
    }
}

3
มัน "ทำงาน" เป็นวิธีการขยาย สิ่งที่น่าสนใจเนื่องจากใช้ได้แม้ว่าคุณจะพูดว่า o.IsDefault <object> () เมื่อ o เป็นโมฆะ น่ากลัว =)
นิค Farina

6

หากต้องการจัดการ T ทุกประเภทรวมถึงที่ T เป็นชนิดดั้งเดิมคุณจะต้องรวบรวมในการเปรียบเทียบทั้งสองวิธี:

    T Get<T>(Func<T> createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }

1
โปรดทราบว่าฟังก์ชั่นได้รับการเปลี่ยนแปลงเพื่อยอมรับ Func <T> และส่งคืน T ซึ่งฉันคิดว่าถูกตัดออกโดยไม่ตั้งใจจากรหัสของผู้ถาม
Nick Farina

ดูเหมือนว่า ReSharper กำลังยุ่งกับฉัน ไม่ได้ตระหนักถึงคำเตือนเกี่ยวกับการเปรียบเทียบที่เป็นไปได้ระหว่างประเภทค่าและ null ไม่ใช่คำเตือนของคอมไพเลอร์
30719 Nathan Ridley

2
FYI: หาก T กลายเป็นประเภทค่าการเปรียบเทียบกับค่า null จะถือว่าเป็นเท็จเสมอโดยตัวสั่น
Eric Lippert

เหมาะสม: รันไทม์จะเปรียบเทียบตัวชี้กับชนิดของค่า การตรวจสอบ Equals () ทำงานได้ในกรณีนั้น (น่าสนใจเนื่องจากดูเหมือนว่าภาษาแบบไดนามิกมากที่จะพูด 5.Equals (4) ซึ่งรวบรวม)
Nick Farina

2
ดู EqualityComparer <T> คำตอบหาทางเลือกที่ไม่เกี่ยวข้องกับมวย et
Marc Gravell

2

จะมีปัญหาที่นี่ -

หากคุณจะอนุญาตให้ใช้งานได้กับทุกประเภทเริ่มต้น (T) จะเป็นโมฆะสำหรับประเภทอ้างอิงและ 0 (หรือ struct เต็ม 0) สำหรับประเภทค่า

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

หรือคุณสามารถใส่ข้อ จำกัด ของอินเทอร์เฟซนี้และอินเทอร์เฟซสามารถให้วิธีการตรวจสอบกับค่าเริ่มต้นของคลาส / struct


1

ฉันคิดว่าคุณอาจต้องแบ่งตรรกะนี้เป็นสองส่วนและตรวจสอบโมฆะก่อน

public static bool IsNullOrEmpty<T>(T value)
{
    if (IsNull(value))
    {
        return true;
    }
    if (value is string)
    {
        return string.IsNullOrEmpty(value as string);
    }
    return value.Equals(default(T));
}

public static bool IsNull<T>(T value)
{
    if (value is ValueType)
    {
        return false;
    }
    return null == (object)value;
}

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

ในวิธี IsNullOrEmpty เรากำลังตรวจสอบกรณีพิเศษของสตริง สำหรับประเภทอื่น ๆ ทั้งหมดเรากำลังเปรียบเทียบค่า (ซึ่งทราบอยู่แล้วว่าไม่เป็นโมฆะ) กับค่าเริ่มต้นซึ่งสำหรับประเภทการอ้างอิงทั้งหมดเป็นโมฆะและสำหรับประเภทค่ามักจะมีรูปแบบเป็นศูนย์

ใช้วิธีการเหล่านี้รหัสต่อไปนี้ทำงานตามที่คุณอาจคาดหวัง:

class Program
{
    public class MyClass
    {
        public string MyString { get; set; }
    }

    static void Main()
    {
        int  i1 = 1;    Test("i1", i1); // False
        int  i2 = 0;    Test("i2", i2); // True
        int? i3 = 2;    Test("i3", i3); // False
        int? i4 = null; Test("i4", i4); // True

        Console.WriteLine();

        string s1 = "hello";      Test("s1", s1); // False
        string s2 = null;         Test("s2", s2); // True
        string s3 = string.Empty; Test("s3", s3); // True
        string s4 = "";           Test("s4", s4); // True

        Console.WriteLine();

        MyClass mc1 = new MyClass(); Test("mc1", mc1); // False
        MyClass mc2 = null;          Test("mc2", mc2); // True
    }

    public static void Test<T>(string fieldName, T field)
    {
        Console.WriteLine(fieldName + ": " + IsNullOrEmpty(field));
    }

    // public static bool IsNullOrEmpty<T>(T value) ...

    // public static bool IsNull<T>(T value) ...
}

1

วิธีการขยายตามคำตอบที่ยอมรับ

   public static bool IsDefault<T>(this T inObj)
   {
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

การใช้งาน:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue == null || tValue.IsDefault()) return false;
   }

สลับเป็น null เพื่อทำให้ง่ายขึ้น:

   public static bool IsNullOrDefault<T>(this T inObj)
   {
       if (inObj == null) return true;
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

การใช้งาน:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue.IsNullOrDefault()) return false;
   }

0

ฉันใช้:

public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}

-1

ไม่ทราบว่าสิ่งนี้ใช้ได้กับความต้องการของคุณหรือไม่ แต่คุณสามารถ จำกัด T ให้เป็นประเภทที่ใช้อินเทอร์เฟซเช่น IComparable แล้วใช้เมธอด ComparesTo () จากอินเทอร์เฟซนั้น (ซึ่ง IIRC รองรับ / จัดการ nulls) เช่นนี้ :

public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))

อาจมีอินเตอร์เฟสอื่น ๆ ที่คุณสามารถใช้เช่นเดียวกับ IEquitable เป็นต้น


OP เป็นกังวลเกี่ยวกับ NullReferenceException และคุณรับประกันเขาเหมือนกัน
nawfal

-2

@ilitirit:

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}

ผู้ประกอบการ '==' ไม่สามารถใช้กับตัวถูกดำเนินการประเภท 'T' และ 'T'

ฉันไม่สามารถคิดถึงวิธีการทำสิ่งนี้ได้หากไม่มีการทดสอบ Null อย่างชัดเจนแล้วตามด้วยการเรียกใช้เมธอด Equals หรือ Object.Equals ตามที่แนะนำไว้ข้างต้น

คุณสามารถคิดค้นวิธีการแก้ปัญหาโดยใช้ System.Comparison แต่จริงๆแล้วมันจะจบลงด้วยวิธีการของบรรทัดที่มากขึ้นของรหัสและเพิ่มความซับซ้อนอย่างมาก


-3

ฉันคิดว่าคุณสนิท

if (myArgument.Equals(default(T)))

ตอนนี้การคอมไพล์ แต่จะล้มเหลวถ้าmyArgumentเป็นโมฆะซึ่งเป็นส่วนหนึ่งของสิ่งที่ฉันกำลังทดสอบ ฉันสามารถเพิ่มการตรวจสอบ null อย่างชัดเจนเช่นนี้:

คุณเพียงแค่ต้องย้อนกลับวัตถุที่เท่ากับถูกเรียกว่าเป็นวิธีที่ปลอดภัยเป็นโมฆะ

default(T).Equals(myArgument);

ฉันคิดในสิ่งเดียวกัน
Chris Gessler

6
ค่าเริ่มต้น (T) ของประเภทการอ้างอิงเป็นโมฆะและผลลัพธ์ในการรับประกัน NullReferenceException
Stefan Steinegger
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.