C # 'คือ' ประสิทธิภาพของตัวดำเนินการ


103

ฉันมีโปรแกรมที่ต้องการประสิทธิภาพที่รวดเร็ว ภายในหนึ่งในลูปด้านในฉันต้องทดสอบประเภทของวัตถุเพื่อดูว่ามันสืบทอดมาจากอินเทอร์เฟซที่แน่นอนหรือไม่

วิธีหนึ่งที่จะทำได้คือใช้ฟังก์ชันการตรวจสอบประเภทในตัวของ CLR วิธีการที่หรูหราที่สุดอาจเป็นคำหลัก 'is':

if (obj is ISpecialType)

อีกวิธีหนึ่งคือการให้คลาสฐานฟังก์ชัน GetType () เสมือนของฉันเองซึ่งส่งคืนค่า enum ที่กำหนดไว้ล่วงหน้า (ในกรณีของฉันจริงๆแล้วฉันต้องการบูลเท่านั้น) วิธีนั้นจะรวดเร็ว แต่ไม่ค่อยหรูหรา

ฉันเคยได้ยินว่ามีคำสั่ง IL สำหรับคีย์เวิร์ด 'is' โดยเฉพาะ แต่นั่นไม่ได้หมายความว่าจะดำเนินการอย่างรวดเร็วเมื่อแปลเป็นแอสเซมบลีเนทีฟ ใครสามารถแบ่งปันข้อมูลเชิงลึกเกี่ยวกับประสิทธิภาพของ 'is' กับวิธีการอื่น ๆ ได้บ้าง?

อัปเดต: ขอบคุณสำหรับคำตอบทั้งหมด! ดูเหมือนว่ามีคำตอบที่เป็นประโยชน์สองสามประเด็น: ประเด็นของ Andrew เกี่ยวกับ 'is' การแสดงโดยอัตโนมัติเป็นสิ่งสำคัญ แต่ข้อมูลประสิทธิภาพที่รวบรวมโดย Binary Worrier และ Ian ก็มีประโยชน์อย่างมากเช่นกัน จะดีมากถ้าคำตอบข้อใดข้อหนึ่งได้รับการแก้ไขเพื่อรวมข้อมูลทั้งหมดนี้


2
btw, CLR จะไม่ให้คุณสร้างฟังก์ชัน Type GetType () ของคุณเองได้เพราะมันทำลายกฎ CLR หลักข้อหนึ่ง - ประเภทอย่างแท้จริง
abatishchev

1
เอ่อฉันไม่แน่ใจว่าคุณหมายถึงอะไรตามกฎ "ประเภทจริงๆ" แต่ฉันเข้าใจว่า CLR มีฟังก์ชัน Type GetType () ในตัว ถ้าฉันจะใช้วิธีนั้นมันจะเป็นฟังก์ชันของชื่ออื่นที่ส่งคืน enum บางส่วนดังนั้นจึงไม่มีความขัดแย้งของชื่อ / สัญลักษณ์ใด ๆ
JubJub

3
ฉันคิดว่า abatishchev หมายถึง "ประเภทความปลอดภัย" GetType () ไม่ใช่แบบเสมือนเพื่อป้องกันไม่ให้ประเภทโกหกเกี่ยวกับตัวเองดังนั้นจึงรักษาความปลอดภัยของประเภทไว้
Andrew Hare

2
คุณได้พิจารณาการดึงข้อมูลล่วงหน้าและการแคชการปฏิบัติตามประเภทเพื่อที่คุณจะได้ไม่ต้องทำภายในลูปหรือไม่? ดูเหมือนว่าคำถามที่สมบูรณ์แบบทุกคำถามมักจะ +1 อย่างหนาแน่น แต่ดูเหมือนว่าฉันจะเข้าใจ c # ไม่ดี ช้าเกินไปจริงหรือ? อย่างไร? คุณได้ลองทำอะไรบ้าง? เห็นได้ชัดว่าไม่ค่อยให้ความเห็นเกี่ยวกับคำตอบของคุณมากนัก ...
Gusdor

คำตอบ:


115

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

หากคุณจะแคสต์ต่อไปนี่คือแนวทางที่ดีกว่า:

ISpecialType t = obj as ISpecialType;

if (t != null)
{
    // use t here
}

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

4
@JubJub: ไม่นะ asโดยทั่วไปความล้มเหลวจะดำเนินการเช่นเดียวกับis(กล่าวคือการตรวจสอบประเภท) แต่ที่แตกต่างก็คือว่ามันแล้วส่งกลับแทนnull false
Konrad Rudolph

74

ฉันอยู่กับเอียนเธอคงไม่อยากทำแบบนี้

อย่างไรก็ตามโปรดทราบว่ามีความแตกต่างเพียงเล็กน้อยระหว่างทั้งสองครั้งโดยมีการทำซ้ำมากกว่า 10,000,000 ครั้ง

  • การตรวจสอบ enum มาที่700 มิลลิวินาที (โดยประมาณ)
  • การตรวจสอบ IS เข้ามาที่ 1,000 มิลลิวินาที (โดยประมาณ)

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

ฐานและคลาสที่ได้รับของฉัน

class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
}

class MyClassA : MyBaseClass
{
    public MyClassA()
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
    }
}
class MyClassB : MyBaseClass
{
    public MyClassB()
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
    }
}

JubJub: ตามที่ร้องขอข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบ

ฉันเรียกใช้การทดสอบทั้งสองจากแอปคอนโซล (การสร้างดีบัก) การทดสอบแต่ละครั้งมีลักษณะดังต่อไปนี้

static void IsTest()
{
    DateTime start = DateTime.Now;
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass a;
        if (i % 2 == 0)
            a = new MyClassA();
        else
            a = new MyClassB();
        bool b = a is MyClassB;
    }
    DateTime end = DateTime.Now;
    Console.WriteLine("Is test {0} miliseconds", (end - start).TotalMilliseconds);
}

เมื่อรันในรีลีสฉันได้รับความแตกต่าง 60 - 70 มิลลิวินาทีเช่นเอียน

อัปเดตเพิ่มเติม - 25 ต.ค. 2555
หลังจากผ่านไปสองปีฉันสังเกตเห็นบางอย่างเกี่ยวกับเรื่องนี้คอมไพเลอร์สามารถเลือกที่จะละเว้นbool b = a is MyClassBในรีลีสได้เนื่องจาก b ไม่ได้ใช้ที่ใดก็ได้

รหัสนี้ . .

public static void IsTest()
{
    long total = 0;
    var a = new MyClassA();
    var b = new MyClassB();
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass baseRef;
        if (i % 2 == 0)
            baseRef = a;//new MyClassA();
        else
            baseRef = b;// new MyClassB();
        //bool bo = baseRef is MyClassB;
        bool bo = baseRef.ClassType == MyBaseClass.ClassTypeEnum.B;
        if (bo) total += 1;
    }
    sw.Stop();
    Console.WriteLine("Is test {0} miliseconds {1}", sw.ElapsedMilliseconds, total);
}

. . . แสดงการisตรวจสอบที่เข้ามาอย่างต่อเนื่องที่ประมาณ 57 มิลลิวินาทีและการเปรียบเทียบ enum เข้ามาที่ 29 มิลลิวินาที

หมายเหตุ ฉันยังคงชอบisเช็คความแตกต่างน้อยเกินไปที่จะใส่ใจ


35
+1 สำหรับการทดสอบประสิทธิภาพจริงแทนที่จะเป็นการสมมติ
Jon Tackabury

3
การทดสอบด้วยคลาสนาฬิกาจับเวลาจะดีกว่ามากแทนที่จะเป็น DateTime ซึ่งตอนนี้มีราคาแพงมาก
abatishchev

2
ฉันจะนำสิ่งนั้นขึ้นเครื่องอย่างไรก็ตามในกรณีนี้ฉันไม่คิดว่ามันจะส่งผลกระทบต่อผลลัพธ์ ขอบคุณ :)
Binary Worrier

11
@Binary Worrier- การจัดสรรคลาสของโอเปอเรเตอร์ใหม่ของคุณกำลังจะบดบังความแตกต่างของประสิทธิภาพในการดำเนินการ 'is' โดยสิ้นเชิง ทำไมคุณไม่ลบการดำเนินการใหม่เหล่านั้นออกโดยการนำอินสแตนซ์ที่จัดสรรไว้ล่วงหน้าสองอินสแตนซ์กลับมาใช้ใหม่จากนั้นเรียกใช้โค้ดใหม่และโพสต์ผลลัพธ์ของคุณ

1
@mcmillab: ฉันจะรับประกันว่าไม่ว่าคุณจะทำอะไรคุณจะมีปัญหาคอขวดมากมายที่มีขนาดใหญ่กว่าการลดประสิทธิภาพการทำงานใด ๆ ที่isผู้ปฏิบัติงานเป็นสาเหตุให้คุณและการที่คุณได้ยินเกี่ยวกับการออกแบบและการเขียนโค้ดรอบตัวผู้ให้isบริการจะต้องเสียเงินมหาศาล คุณภาพของรหัสและท้ายที่สุดก็จะเอาชนะประสิทธิภาพด้วยตนเองได้เช่นกัน ในกรณีนี้ฉันยืนตามคำสั่งของฉัน 'การเป็น' ผู้ประกอบการจะไม่เคยไปได้ปัญหาเกี่ยวกับประสิทธิภาพการทำงานรันไทม์ของคุณ
Binary Worrier

23

โอเคฉันกำลังคุยเรื่องนี้กับใครบางคนและตัดสินใจที่จะทดสอบสิ่งนี้เพิ่มเติม เท่าที่ผมสามารถบอกได้ประสิทธิภาพการทำงานของasและisมีทั้งที่ดีมากเมื่อเทียบกับการทดสอบสมาชิกของคุณเองหรือฟังก์ชั่นข้อมูลประเภทร้านค้า

ฉันใช้Stopwatchซึ่งฉันเพิ่งเรียนรู้อาจไม่ใช่แนวทางที่น่าเชื่อถือที่สุดดังนั้นฉันจึงลองUtcNowด้วย ต่อมาฉันได้ลองใช้วิธีเวลาของโปรเซสเซอร์ซึ่งดูเหมือนว่าจะUtcNowรวมเวลาสร้างที่คาดเดาไม่ได้ด้วย ฉันยังพยายามทำให้คลาสพื้นฐานไม่ใช่นามธรรมโดยไม่มีเวอร์ชวล แต่ดูเหมือนจะไม่มีผลอย่างมีนัยสำคัญ

ฉันรันบน Quad Q6600 พร้อม RAM 16GB แม้ว่าจะมีการทำซ้ำ 50mil แต่ตัวเลขก็ยังคงตีกลับประมาณ +/- 50 หรือมากกว่านั้นในระดับมิลลิวินาทีดังนั้นฉันจะไม่อ่านความแตกต่างเล็กน้อยมากเกินไป

มันน่าสนใจที่จะเห็นว่า x64 สร้างได้เร็วกว่า แต่ดำเนินการเป็น / ช้ากว่า x86

x64 โหมดการเปิดตัว:
นาฬิกาจับเวลา:
As: 561ms
Is: 597ms
Base property: 539ms
Base field: 555ms
Base RO field: 552ms
Virtual GetEnumType () test: 556ms
Virtual IsB () test: 588ms
Create Time: 10416ms

UtcNow:
As: 499ms
Is: 532ms
Base property: 479ms
Base field: 502ms
Base RO field: 491ms
Virtual GetEnumType (): 502ms
Virtual bool IsB (): 522ms
Create Time: 285ms (ตัวเลขนี้ดูไม่น่าเชื่อถือกับ UtcNow ฉันยังได้ 109ms ด้วย และ 806ms.)

x86 โหมดการเปิดตัว:
นาฬิกาจับเวลา:
As: 391ms
Is: 423ms
Base property: 369ms
Base field: 321ms
Base RO field: 339ms
Virtual GetEnumType () test: 361ms
Virtual IsB () test: 365ms
Create Time: 14106ms

UtcNow:
เป็น: 348ms
Is: 375ms
Base property: 329ms
Base field: 286ms
Base RO field: 309ms
Virtual GetEnumType (): 321ms
Virtual bool IsB (): 332ms
Create Time: 544ms (ตัวเลขนี้ดูไม่น่าเชื่อถือกับ UtcNow)

นี่คือรหัสส่วนใหญ่:

    static readonly int iterations = 50000000;
    void IsTest()
    {
        Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1;
        MyBaseClass[] bases = new MyBaseClass[iterations];
        bool[] results1 = new bool[iterations];

        Stopwatch createTime = new Stopwatch();
        createTime.Start();
        DateTime createStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            if (i % 2 == 0) bases[i] = new MyClassA();
            else bases[i] = new MyClassB();
        }
        DateTime createStop = DateTime.UtcNow;
        createTime.Stop();


        Stopwatch isTimer = new Stopwatch();
        isTimer.Start();
        DateTime isStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] =  bases[i] is MyClassB;
        }
        DateTime isStop = DateTime.UtcNow; 
        isTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch asTimer = new Stopwatch();
        asTimer.Start();
        DateTime asStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i] as MyClassB != null;
        }
        DateTime asStop = DateTime.UtcNow; 
        asTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch baseMemberTime = new Stopwatch();
        baseMemberTime.Start();
        DateTime baseStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassType == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseStop = DateTime.UtcNow;
        baseMemberTime.Stop();
        CheckResults(ref  results1);

        Stopwatch baseFieldTime = new Stopwatch();
        baseFieldTime.Start();
        DateTime baseFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseFieldStop = DateTime.UtcNow;
        baseFieldTime.Stop();
        CheckResults(ref  results1);


        Stopwatch baseROFieldTime = new Stopwatch();
        baseROFieldTime.Start();
        DateTime baseROFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseROFieldStop = DateTime.UtcNow;
        baseROFieldTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethTime = new Stopwatch();
        virtMethTime.Start();
        DateTime virtStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].GetClassType() == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime virtStop = DateTime.UtcNow;
        virtMethTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethBoolTime = new Stopwatch();
        virtMethBoolTime.Start();
        DateTime virtBoolStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].IsB();
        }
        DateTime virtBoolStop = DateTime.UtcNow;
        virtMethBoolTime.Stop();
        CheckResults(ref  results1);


        asdf.Text +=
        "Stopwatch: " + Environment.NewLine 
          +   "As:  " + asTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           +"Is:  " + isTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           + "Base property:  " + baseMemberTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base field:  " + baseFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base RO field:  " + baseROFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType() test:  " + virtMethTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual IsB() test:  " + virtMethBoolTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Create Time :  " + createTime.ElapsedMilliseconds + "ms" + Environment.NewLine + Environment.NewLine+"UtcNow: " + Environment.NewLine + "As:  " + (asStop - asStart).Milliseconds + "ms" + Environment.NewLine + "Is:  " + (isStop - isStart).Milliseconds + "ms" + Environment.NewLine + "Base property:  " + (baseStop - baseStart).Milliseconds + "ms" + Environment.NewLine + "Base field:  " + (baseFieldStop - baseFieldStart).Milliseconds + "ms" + Environment.NewLine + "Base RO field:  " + (baseROFieldStop - baseROFieldStart).Milliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType():  " + (virtStop - virtStart).Milliseconds + "ms" + Environment.NewLine + "Virtual bool IsB():  " + (virtBoolStop - virtBoolStart).Milliseconds + "ms" + Environment.NewLine + "Create Time :  " + (createStop-createStart).Milliseconds + "ms" + Environment.NewLine;
    }
}

abstract class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
    public ClassTypeEnum ClassTypeField;
    public readonly ClassTypeEnum ClassTypeReadonlyField;
    public abstract ClassTypeEnum GetClassType();
    public abstract bool IsB();
    protected MyBaseClass(ClassTypeEnum kind)
    {
        ClassTypeReadonlyField = kind;
    }
}

class MyClassA : MyBaseClass
{
    public override bool IsB() { return false; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.A; }
    public MyClassA() : base(MyBaseClass.ClassTypeEnum.A)
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
        ClassTypeField = MyBaseClass.ClassTypeEnum.A;            
    }
}
class MyClassB : MyBaseClass
{
    public override bool IsB() { return true; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.B; }
    public MyClassB() : base(MyBaseClass.ClassTypeEnum.B)
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
        ClassTypeField = MyBaseClass.ClassTypeEnum.B;
    }
}

45
(โบนัสบางอย่างเช็คสเปียร์ 5 แรงบันดาลใจ ... ) จะเป็นหรือไม่เป็น: นั่นคือคำถาม: ไม่ว่าจะเป็น 'ผู้สูงศักดิ์ในรหัสที่ต้องทนทุกข์ทรมานจากการแจงนับและคุณสมบัติของฐานนามธรรมหรือเพื่อรับข้อเสนอของตัวกลาง นักภาษาศาสตร์และโดยการเรียกร้องคำสั่งเชื่อพวกเขา? การคาดเดา: สงสัย; ไม่มีอีกแล้ว; และเมื่อถึงเวลาที่จะเข้าใจเราก็ยุติความปวดหัวและความพิศวงในจิตใต้สำนึกนับพันที่นักเขียนโค้ดที่มีเวลา จำกัด เป็นทายาท 'เป็นการปิดที่นับถือศรัทธาที่ต้องการ ให้ตายไม่ แต่จะนอน ใช่ฉันจะนอนหลับโอกาสที่จะฝันเป็นและในสิ่งที่อาจได้มาจากพื้นฐานส่วนใหญ่ของชั้นเรียน
Jared Thirsk

เราสามารถสรุปได้จากสิ่งนี้ว่าการเข้าถึงคุณสมบัติเร็วขึ้นบน x64 แล้วเข้าถึงฟิลด์ !!! นั่นเป็นเรื่องที่น่าแปลกใจสำหรับฉันว่ามันเป็นไปได้อย่างไร?
Didier A.

1
ฉันจะไม่สรุปอย่างนั้นเพราะ: "แม้จะมีการทำซ้ำ 50mil แต่ตัวเลขก็ยังคงตีกลับประมาณ +/- 50 หรือมากกว่านั้นในระดับมิลลิวินาทีดังนั้นฉันจะไม่อ่านความแตกต่างเล็กน้อยมากเกินไป"
Jared Thirsk

16

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

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

เช่น Obj สามารถเป็น ISpecialType หรือ IType;

ทั้งสองมีวิธี DoStuff () กำหนดไว้ สำหรับ IType มันสามารถส่งคืนหรือทำสิ่งที่กำหนดเองได้ในขณะที่ ISpecialType สามารถทำสิ่งอื่นได้

จากนั้นจะลบการแคสต์ทั้งหมดทำให้โค้ดสะอาดขึ้นและดูแลรักษาง่ายขึ้นและชั้นเรียนรู้วิธีทำงานของตัวเอง


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

ฉันได้ทำการทดสอบที่คล้ายกันกับ Binary Worrier หลังจากความคิดเห็นของ abatishchev และพบความแตกต่างเพียง 60ms จาก 10,000,000 itterations
เอียน

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

15

ฉันทำการเปรียบเทียบประสิทธิภาพกับความเป็นไปได้สองแบบของการเปรียบเทียบประเภท

  1. myobject.GetType () == typeof (MyClass)
  2. myobject คือ MyClass

ผลลัพธ์คือ: ใช้ "is" เร็วขึ้นประมาณ 10 เท่า !!!

เอาท์พุต:

เวลาเปรียบเทียบประเภท: 00: 00: 00.456

เวลาสำหรับการเปรียบเทียบคือ 00: 00: 00.042 น

รหัสของฉัน:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication3
{
    class MyClass
    {
        double foo = 1.23;
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass myobj = new MyClass();
            int n = 10000000;

            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj.GetType() == typeof(MyClass);
            }

            sw.Stop();
            Console.WriteLine("Time for Type-Comparison: " + GetElapsedString(sw));

            sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj is MyClass;
            }

            sw.Stop();
            Console.WriteLine("Time for Is-Comparison: " + GetElapsedString(sw));
        }

        public static string GetElapsedString(Stopwatch sw)
        {
            TimeSpan ts = sw.Elapsed;
            return String.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
        }
    }
}

13

Point Andrew Hare ทำเกี่ยวกับประสิทธิภาพที่หายไปเมื่อคุณทำการisตรวจสอบแล้วแคสต์นั้นถูกต้อง แต่ใน C # 7.0 ที่เราทำได้คือตรวจสอบรูปแบบแม่มดเพื่อหลีกเลี่ยงการร่ายเพิ่มเติมในภายหลัง:

if (obj is ISpecialType st)
{
   //st is in scope here and can be used
}

นอกจากนี้หากคุณต้องการตรวจสอบระหว่างโครงสร้างการจับคู่รูปแบบ C # 7.0 หลายประเภทตอนนี้อนุญาตให้คุณทำswitchในประเภท:

public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับการจับคู่รูปแบบใน C # ในเอกสารที่นี่


1
แน่นอนว่าเป็นวิธีแก้ปัญหาที่ถูกต้อง แต่คุณลักษณะการจับคู่รูปแบบ C # นี้ทำให้ฉันเสียใจเมื่อมีการส่งเสริมโค้ด "คุณลักษณะอิจฉา" เช่นนี้ แน่นอนว่าเราควรจะดิ้นรนเพื่อห่อหุ้มลอจิกที่มีเพียงออบเจ็กต์ที่ได้รับเท่านั้นที่ "รู้" วิธีคำนวณพื้นที่ของตัวเองแล้วพวกมันก็คืนค่า?
Dib

2
ดังนั้นต้องใช้ปุ่มตัวกรอง (ในคำถาม) สำหรับคำตอบที่ใช้กับเฟรมเวิร์กแพลตฟอร์ม ฯลฯ เวอร์ชันใหม่กว่าคำตอบนี้เป็นพื้นฐานของตัวกรองที่ถูกต้องสำหรับ C # 7
Nick Westgate

2
@Dib OOP อุดมคติถูกโยนออกไปนอกหน้าต่างเมื่อคุณทำงานกับประเภท / คลาส / อินเทอร์เฟซที่คุณไม่ได้ควบคุม วิธีนี้ยังมีประโยชน์สำหรับเมื่อจัดการกับผลลัพธ์ของฟังก์ชันที่สามารถส่งคืนค่าหนึ่งในหลาย ๆ ค่าของประเภทที่แตกต่างกันโดยสิ้นเชิง (เนื่องจาก C # ยังไม่รองรับประเภทยูเนี่ยนคุณสามารถใช้ไลบรารีได้เช่นกันOneOf<T...>แต่มีข้อบกพร่องที่สำคัญ) .
ได

5

ในกรณีที่ใครสงสัยฉันได้ทำการทดสอบใน Unity engine 2017.1 ด้วยสคริปต์รันไทม์เวอร์ชัน. NET4.6 (Experimantal) บนโน้ตบุ๊กที่ใช้ CPU i5-4200U ผล:

Average Relative To Local Call LocalCall 117.33 1.00 is 241.67 2.06 Enum 139.33 1.19 VCall 294.33 2.51 GetType 276.00 2.35

บทความเต็ม: http://www.ennoble-studios.com/tuts/unity-c-performance-comparison-is-vs-enum-vs-virtual-call.html


ลิงค์บทความตายแล้ว
James Wilkins

ลิงก์ @ เจมส์ฟื้นขึ้นมา
กรุ

สิ่งที่ดี - แต่ฉันไม่ได้โหวตให้คุณ (อันที่จริงฉันก็โหวตอยู่ดี); ในกรณีที่คุณสงสัย. :)
James Wilkins

-3

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

เช่น Obj สามารถเป็น ISpecialType หรือ IType;

ทั้งสองมีวิธี DoStuff () กำหนดไว้ สำหรับ IType มันสามารถส่งคืนหรือทำสิ่งที่กำหนดเองได้ในขณะที่ ISpecialType สามารถทำสิ่งอื่นได้

จากนั้นจะลบการแคสต์ทั้งหมดทำให้โค้ดสะอาดขึ้นและดูแลรักษาง่ายขึ้นและชั้นเรียนรู้วิธีทำงานของตัวเอง


1
สิ่งนี้ไม่ตอบคำถาม อย่างไรก็ตามชั้นเรียนอาจไม่ทราบวิธีการประมวลผลด้วยตนเองเสมอไปเนื่องจากขาดบริบท เราใช้ตรรกะที่คล้ายกันกับการจัดการข้อยกเว้นเมื่อเราอนุญาตให้มีข้อยกเว้นขึ้นสายการโทรจนกว่าวิธีการ / ฟังก์ชันบางอย่างจะมีบริบทเพียงพอที่จะจัดการกับข้อผิดพลาด
Vakhtang
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.