แคสติ้ง vs การใช้คำว่า 'as' ใน CLR


386

เมื่อมีการตั้งโปรแกรมอินเทอร์เฟซฉันพบว่าฉันทำการแปลงหรือแปลงวัตถุเป็นจำนวนมาก

การแปลงสองวิธีนี้มีความแตกต่างกันหรือไม่? ถ้าเป็นเช่นนั้นจะมีความแตกต่างของค่าใช้จ่ายหรือสิ่งนี้มีผลต่อโปรแกรมของฉันอย่างไร

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

นอกจากนี้วิธีการที่แนะนำ "โดยทั่วไป" คืออะไร?


คุณสามารถเพิ่มตัวอย่างเล็ก ๆ ของสาเหตุที่คุณใช้ cast ในตอนแรกกับคำถามหรืออาจจะเริ่มใหม่ ฉันสนใจว่าทำไมคุณถึงต้องการนักแสดงเพื่อทดสอบหน่วยเท่านั้น ฉันคิดว่ามันอยู่นอกเหนือขอบเขตของคำถามนี้
Erik van Brakel

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

ในฐานะที่เป็น Patrik Hägneชาญฉลาดชี้ให้เห็นด้านล่างมีเป็นความแตกต่าง
Neil

คำตอบ:


517

คำตอบด้านล่างบรรทัดถูกเขียนในปี 2008

แนะนำการจับคู่รูปแบบ C # 7 ซึ่งแทนที่ตัวasดำเนินการส่วนใหญ่ตามที่คุณสามารถเขียน:

if (randomObject is TargetType tt)
{
    // Use tt here
}

โปรดทราบว่าttยังคงอยู่ในขอบเขตหลังจากนี้ แต่ไม่ได้รับการกำหนดแน่นอน (มันถูกกำหนดภายในifร่างกายอย่างแน่นอน) มันน่ารำคาญเล็กน้อยในบางกรณีดังนั้นถ้าคุณสนใจแนะนำจำนวนตัวแปรที่เล็กที่สุดเท่าที่จะเป็นไปได้ในทุก ๆ ขอบเขตคุณอาจต้องการใช้isตามด้วยตัวละคร


ฉันไม่คิดว่าคำตอบใด ๆ (ในขณะที่เริ่มคำตอบนี้!) ได้อธิบายจริงๆว่ามันคุ้มค่ากับการใช้งานที่ไหน

  • อย่าทำสิ่งนี้:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }
    

    การตรวจสอบนี้ไม่เพียงสองครั้ง แต่อาจตรวจสอบสิ่งต่าง ๆ หากrandomObjectเป็นเขตข้อมูลแทนที่จะเป็นตัวแปรเฉพาะที่ เป็นไปได้ที่ "ถ้า" จะผ่าน แต่จากนั้นการโยนจะล้มเหลวหากเธรดอื่นเปลี่ยนค่าrandomObjectระหว่างสอง

  • ถ้าrandomObjectมันควรจะเป็นตัวอย่างของการTargetTypeเช่นถ้ามันไม่ได้หมายความว่ามีข้อผิดพลาดแล้วหล่อเป็นโซลูชั่นที่เหมาะสม นั่นทำให้เกิดข้อผิดพลาดในทันทีซึ่งหมายความว่าจะไม่ทำงานอีกต่อไปภายใต้สมมติฐานที่ไม่ถูกต้องและข้อยกเว้นจะแสดงประเภทของบั๊กอย่างถูกต้อง

    // This will throw an exception if randomObject is non-null and
    // refers to an object of an incompatible type. The cast is
    // the best code if that's the behaviour you want.
    TargetType convertedRandomObject = (TargetType) randomObject;
    
  • หากrandomObject อาจเป็นตัวอย่างของTargetTypeและTargetTypeเป็นประเภทอ้างอิงให้ใช้รหัสดังนี้:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
    
  • หากrandomObject อาจเป็นตัวอย่างของTargetTypeและTargetTypeเป็นประเภทค่าเราไม่สามารถใช้asกับTargetTypeตัวเองได้ แต่เราสามารถใช้ประเภทที่ไม่สามารถใช้ได้:

    TargetType? convertedRandomObject = randomObject as TargetType?;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject.Value
    }
    

    (หมายเหตุ: ปัจจุบันนี้ช้ากว่า + castจริง ๆ แล้วฉันคิดว่ามันดูดีและมีความสอดคล้องกันมากขึ้น แต่เราไปกันแล้ว)

  • หากคุณไม่ต้องการค่าที่แปลง แต่คุณต้องรู้ว่าเป็นตัวอย่างของ TargetType หรือไม่isผู้ดำเนินการคือเพื่อนของคุณ ในกรณีนี้มันไม่สำคัญว่า TargetType เป็นประเภทการอ้างอิงหรือประเภทค่า

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

  • ฉันเกือบจะใช้แน่นอนisสำหรับกรณีประเภทค่าก่อนหน้านี้ตอนนี้ไม่ได้คิดถึงการใช้ประเภท nullable และasร่วมกัน :)


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

ตามคำตอบของ naasking นั้นเป็น and-cast หรือ is-and-as ทั้งสองมีความรวดเร็วเท่ากับ as-and-null-check กับ JIT ที่ทันสมัยดังแสดงในรหัสด้านล่าง:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "x";
            values[i + 2] = new object();
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);
    }

    static void FindLengthWithIsAndCast(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string) o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

ในแล็ปท็อปของฉันสิ่งเหล่านี้ทำงานในเวลาประมาณ 60ms สองสิ่งที่ควรทราบ:

  • ไม่มีความแตกต่างอย่างมีนัยสำคัญระหว่างพวกเขา (ในความเป็นจริงมีสถานการณ์ที่เป็นบวก null ตรวจสอบแน่นอนคือช้าลงรหัสข้างต้นจริงทำให้ประเภทการตรวจสอบง่ายเพราะมันเป็นสำหรับการเรียนการประทับตรา. ถ้าคุณตรวจสอบกำลังสำหรับอินเตอร์เฟซและเคล็ดลับความสมดุลเล็กน้อย ในความโปรดปรานของ as-plus-null-check.)
  • พวกเขาทั้งหมดเร็วเมามัน สิ่งนี้จะไม่เป็นคอขวดในรหัสของคุณเว้นแต่ว่าคุณจะไม่ทำอะไรกับค่าในภายหลัง

ดังนั้นไม่ต้องกังวลกับประสิทธิภาพ มากังวลเรื่องความถูกต้องและความสม่ำเสมอกันเถอะ

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

ฉันยังคงยืนยันว่าการตรวจสอบตามมาเป็นโมฆะจะช่วยแยกข้อกังวลได้ดียิ่งขึ้น เรามีหนึ่งคำสั่งที่พยายามแปลงแล้วหนึ่งคำสั่งที่ใช้ผลลัพธ์ is-and-cast หรือ is-and-as ทำการทดสอบแล้วลองอีกครั้งเพื่อแปลงค่า

หากต้องการกล่าวอีกวิธีหนึ่งไม่มีใครเคยเขียน:

int value;
if (int.TryParse(text, out value))
{
    value = int.Parse(text);
    // Use value
}

นั่นคือสิ่งที่ - และ - โยนกำลังทำ - แม้ว่าจะเห็นได้ชัดว่าค่อนข้างถูกกว่า


7
นี่คือค่าใช้จ่ายของการเป็น / เป็น / การหล่อในแง่ของ IL: atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
ฐานของ

3
ในกรณีที่ถ้า targetObject อาจเป็นประเภทเป้าหมายทำไมการใช้ "เป็น" และการรวมกันของนักแสดงถือว่าการปฏิบัติที่ไม่ดี? ฉันหมายถึงมันสร้างโค้ดที่ช้ากว่า แต่ในกรณีนี้ความตั้งใจชัดเจนกว่า AS cast เช่น "ทำอะไรถ้า targetObject คือ targetType" แทนที่จะ "ทำบางอย่างถ้า targetObject เป็นโมฆะ" นอกจากนี้ AS clause จะสร้างตัวแปรที่ไม่จำเป็น ออกจากขอบเขต IF
Valera Kolupaev

2
@Valera: คะแนนที่ดีแม้ว่าฉันจะแนะนำว่าการทดสอบแบบ as / null นั้นมีความหมายมากพอที่ความตั้งใจควรชัดเจนสำหรับนักพัฒนา C # เกือบทั้งหมด ฉันไม่ชอบความซ้ำซ้อนที่เกี่ยวข้องกับการแสดง + เป็นการส่วนตัว ฉันต้องการสร้าง "as-if" ซึ่งทำทั้งสองอย่างในที่เดียว พวกเขาไปด้วยกันบ่อยครั้ง ...
Jon Skeet

2
@ จอน Skeet: ขอโทษสำหรับความล่าช้าของฉัน Is And Cast: 2135, Is And As: 2145, As And null ตรวจสอบ: 1961, รายละเอียด: ระบบปฏิบัติการ: Windows Seven, CPU: i5-520M, 4GB of DDR3 1033 ram, มาตรฐานในอาร์เรย์ จำนวน 128,000,000 รายการ
Behrooz

2
ด้วย C # 7 คุณสามารถทำได้: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}หรือใช้switch/ case ดูเอกสาร
WerWet

71

"เป็น"จะส่งคืนค่า NULL หากไม่สามารถส่งได้

การคัดเลือกก่อนจะยกข้อยกเว้น

สำหรับประสิทธิภาพการเพิ่มข้อยกเว้นมักจะเสียเวลามาก


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

17
ค่าใช้จ่ายในการเพิ่มข้อยกเว้นเป็นปัจจัยที่ต้องพิจารณา แต่บ่อยครั้งเป็นการออกแบบที่ถูกต้อง
Jeffrey L Whitledge

@panesofglass - สำหรับประเภทการอ้างอิงความเข้ากันได้ของการแปลงจะได้รับการตรวจสอบเสมอในเวลาทำงานสำหรับทั้งแบบ as และ cast ดังนั้นปัจจัยดังกล่าวจะไม่แยกความแตกต่างระหว่างสองตัวเลือก (ในกรณีนี้ไม่ได้เป็นเช่นนั้นแล้วโยนไม่สามารถเพิ่มการยกเว้น.)
เจฟฟรีย์ L Whitledge

4
@Frank - หากคุณจำเป็นต้องใช้คอลเลกชันพรีเจนส์ล่วงหน้าและวิธีการใน API ของคุณต้องมีรายชื่อพนักงานและโจ๊กเกอร์บางคนแทนที่จะส่งรายการของผลิตภัณฑ์ดังนั้นข้อยกเว้นที่ไม่ถูกต้องอาจเหมาะสมที่จะส่งสัญญาณ การละเมิดข้อกำหนดของอินเทอร์เฟซ
Jeffrey L Whitledge

27

นี่คือคำตอบอื่นด้วยการเปรียบเทียบ IL พิจารณาชั้นเรียน:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

ตอนนี้ดูที่ IL แต่ละวิธีสร้าง แม้ว่ารหัส op ไม่ได้มีความหมายอะไรกับคุณคุณสามารถเห็นความแตกต่างที่สำคัญอย่างหนึ่งคือ isinst ถูกเรียกตามด้วย castclass ในวิธี DirectCast ดังนั้นสองสายแทนหนึ่งโดยทั่วไป

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

คำหลัก isinst กับ castclass

โพสต์บล็อกนี้มีการเปรียบเทียบที่ดีระหว่างสองวิธีในการทำ บทสรุปของเขาคือ:

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

ฉันมักจะใช้ส่วนตัวเป็นเพราะมันง่ายต่อการอ่านและได้รับการแนะนำโดยทีมพัฒนา. NET (หรือ Jeffrey Richter อยู่แล้ว)


ฉันกำลังมองหาคำอธิบายที่ชัดเจนสำหรับการคัดเลือก vs ในขณะที่คำตอบนี้ทำให้มันชัดเจนขึ้นเนื่องจากเกี่ยวข้องกับการอธิบายคำอธิบายทีละขั้นตอนเป็นภาษากลางขั้นกลาง ขอบคุณ!
มอร์ส

18

หนึ่งในความแตกต่างที่ลึกซึ้งยิ่งขึ้นระหว่างทั้งสองคือคำหลัก "as" ไม่สามารถใช้สำหรับการแคสต์ได้เมื่อผู้ใช้งานแคสต์มีส่วนเกี่ยวข้อง:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

สิ่งนี้จะไม่รวบรวม (แม้ว่าฉันคิดว่ามันทำในรุ่นก่อนหน้า) ในบรรทัดสุดท้ายเนื่องจากคำหลัก "เป็น" ไม่ได้พิจารณาตัวดำเนินการที่นำแสดงโดย สายใช้string cast = (string)f;งานได้ดีแม้ว่า


12

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

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

ในทางกลับกันการใช้งาน C สไตล์จะมีข้อยกเว้นเมื่อไม่สามารถทำการแปลงได้


4
เทียบเท่าใช่ แต่ไม่เหมือนกัน วิธีนี้จะทำให้เกิดโค้ดมากขึ้น
แท่น

10

ไม่ใช่คำตอบสำหรับคำถามของคุณ แต่สิ่งที่ฉันคิดว่าเป็นจุดสำคัญที่เกี่ยวข้อง

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


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

เห็นด้วยกับคางคกฉันยังสงสัยว่าทำไมด้านการทดสอบหน่วยมีความเกี่ยวข้องกับการคัดเลือกให้คุณ @ Frank V. ในกรณีที่มีความจำเป็นในการคัดเลือกนักแสดงมักจะมีความต้องการในการออกแบบใหม่หรือ refactor ตามที่แนะนำว่าคุณพยายาม เพื่อแก้ปัญหาที่แตกต่างกันซึ่งควรจัดการแตกต่างกัน
วุฒิสมาชิก

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

9

โปรดเพิกเฉยต่อคำแนะนำของ Jon Skeet อีกครั้ง: หลีกเลี่ยงรูปแบบการทดสอบและโยนเช่น

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

แนวคิดที่ว่าค่าใช้จ่ายนี้มีค่ามากกว่าการโยนและการทดสอบแบบ null คือMYTH :

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

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

หากคุณต้องการเหตุผลที่การทดสอบและนำแสดงโดยเร็วขึ้นหรืออย่างน้อยก็ไม่ช้าก็มีเหตุผลที่ง่ายและซับซ้อน

ง่าย ๆ :แม้แต่คอมไพเลอร์ที่ไร้เดียงสาจะรวมการทำงานที่คล้ายกันสองรายการเข้าด้วยกันเช่นการทดสอบและการโยนเข้าการทดสอบเดี่ยวและการแยกสาขา cast-and-null-test อาจบังคับให้ทำการทดสอบสองรายการและสาขาหนึ่งสำหรับการทดสอบประเภทและการแปลงเป็นโมฆะเมื่อล้มเหลวหนึ่งสำหรับการตรวจสอบโมฆะตัวเอง อย่างน้อยที่สุดพวกเขาทั้งคู่จะปรับให้เหมาะกับการทดสอบเดี่ยวและสาขาดังนั้นการทดสอบและการคัดเลือกจะไม่ช้าหรือเร็วกว่าการทดสอบแบบคาสต์และโมฆะ

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

ดังนั้นโปรดให้คำแนะนำ "cast-and-null-test นี้ดีกว่าคำแนะนำในการทดสอบและโยน" DIE โปรด. การทดสอบและการโยนนั้นปลอดภัยกว่าและเร็วกว่า


7
@naasking: หากคุณทดสอบสองครั้ง (ตามตัวอย่างแรกของคุณ) มีโอกาสที่ประเภทจะเปลี่ยนระหว่างการทดสอบทั้งสองหากเป็นเขตข้อมูลหรือrefพารามิเตอร์ มันปลอดภัยสำหรับตัวแปรโลคอล แต่ไม่ใช่สำหรับฟิลด์ ฉันสนใจที่จะใช้งานการวัดประสิทธิภาพของคุณ แต่รหัสที่คุณให้ไว้ในโพสต์บล็อกของคุณยังไม่สมบูรณ์ ฉันเห็นด้วยกับการไม่เพิ่มประสิทธิภาพแบบไมโคร แต่ฉันไม่คิดว่าการใช้ค่าสองครั้งใด ๆ ที่สามารถอ่านได้หรือสง่างามกว่าการใช้ "เป็น" และการทดสอบแบบไร้ค่า (แน่นอนฉันจะใช้หล่อตรงมากกว่า "ขณะที่" หลังจากมีครับ.)
จอน Skeet

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

1
+1 สำหรับการชี้ให้เห็นว่า / cast หรือ as / cast นั้นไม่ได้ช้ากว่าความเป็นจริง มีการเรียกใช้การทดสอบที่สมบูรณ์ตัวเองผมสามารถยืนยันได้ก็จะทำให้ไม่แตกต่างกันเท่าที่ผมสามารถมองเห็น - และตรงไปตรงมาคุณสามารถเรียกใช้เหลือเชื่อจำนวนบรรยากาศในเวลาที่มีขนาดเล็กมาก จะอัปเดตคำตอบของฉันด้วยรหัสเต็ม
Jon Skeet

1
แน่นอนถ้าการผูกไม่ใช่ท้องถิ่นมีโอกาสของข้อผิดพลาด TOCTTOU (เวลาของการตรวจสอบการใช้งานเวลา) จุดดีดังนั้นมี สำหรับเหตุผลที่ปลอดภัยฉันทำงานกับนักพัฒนารุ่นใหม่จำนวนมากที่ต้องการนำคนในพื้นที่กลับมาใช้ใหม่ด้วยเหตุผลบางอย่าง cast-and-null นั้นเป็นอันตรายอย่างแท้จริงในประสบการณ์ของฉันและฉันไม่เคยเจอสถานการณ์ TOCTTOU เลยเพราะฉันไม่ได้ออกแบบรหัสของฉัน สำหรับความเร็วในการทดสอบรันไทม์มันเร็วกว่าการจัดส่งแบบเสมือนจริง [1]! เรื่องรหัสฉันจะดูว่าฉันสามารถหาแหล่งที่มาสำหรับการทดสอบส่ง [1] Higherlogics.blogspot.com/2008/10/…
naasking

1
@ นาสซิ่ง: ฉันไม่เคยพบปัญหาการใช้ซ้ำในท้องถิ่น - แต่ฉันว่ามันง่ายกว่าที่จะตรวจสอบโค้ดมากกว่าข้อผิดพลาด TOCTTOU ที่ลึกซึ้งยิ่งขึ้น มันก็คุ้มค่าที่ชี้ให้เห็นว่าฉันเพิ่งทำการทดสอบเบนช์มาร์กของฉันเองเพื่อหาอินเทอร์เฟซแทนคลาสที่ถูกปิดผนึกและนั่นเป็นคำแนะนำเกี่ยวกับประสิทธิภาพในการใช้งาน ทำไมฉันถึงเลือกวิธีการเฉพาะที่นี่
Jon Skeet

4

หากการส่งล้มเหลวคำหลัก 'as' จะไม่ส่งข้อยกเว้น มันตั้งค่าตัวแปรให้เป็นโมฆะ (หรือค่าเริ่มต้นสำหรับประเภทค่า) แทน


3
ไม่มีค่าเริ่มต้นสำหรับประเภทค่า ไม่สามารถใช้สำหรับการหล่อประเภทค่าได้
Patrik Hägne

2
คำหลัก 'ตาม' ไม่ทำงานกับประเภทค่าจริง ๆ ดังนั้นจึงตั้งค่าเป็นศูนย์เสมอ
Erik van Brakel

4

นี่ไม่ใช่คำตอบสำหรับคำถาม แต่แสดงความคิดเห็นกับตัวอย่างรหัสของคำถาม:

โดยปกติแล้วคุณไม่ควรใช้งาน Object จากเช่น IMyInterface to MyClass สิ่งที่ยอดเยี่ยมเกี่ยวกับอินเทอร์เฟซคือถ้าคุณนำวัตถุเป็นอินพุทที่ใช้อินเทอร์เฟซมากกว่าที่คุณไม่ต้องสนใจว่าคุณได้รับวัตถุชนิดใด

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

ตอนนี้คำแนะนำของฉัน: ถ้าอินเทอร์เฟซของคุณถูกออกแบบมาอย่างดีคุณสามารถหลีกเลี่ยง typecasting มากมาย


3

asผู้ประกอบการเท่านั้นที่สามารถใช้กับประเภทอ้างอิงก็ไม่สามารถจะมากเกินไปและมันจะกลับมาnullถ้าดำเนินการล้มเหลว มันจะไม่มีวันยกเว้น

การร่ายสามารถใช้กับชนิดที่เข้ากันได้มันสามารถโอเวอร์โหลดและมันจะส่งข้อยกเว้นหากการดำเนินการล้มเหลว

ทางเลือกที่จะใช้ขึ้นอยู่กับสถานการณ์ ในขั้นต้นเป็นเรื่องของว่าคุณต้องการที่จะโยนข้อยกเว้นในการแปลงล้มเหลว


1
'as' ยังสามารถใช้กับประเภทค่า nullable ซึ่งเป็นรูปแบบที่น่าสนใจ ดูคำตอบของฉันสำหรับรหัส
Jon Skeet

1

คำตอบของฉันเป็นเพียงเกี่ยวกับความเร็วในกรณีที่เราไม่ตรวจสอบประเภทและเราไม่ตรวจสอบ null หลังจากแคสต์ ฉันได้เพิ่มการทดสอบเพิ่มเติมสองรายการลงในโค้ดของ Jon Skeet:

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    static void FindLengthWithIsAndCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string)o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

ผลลัพธ์:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

อย่าพยายามเน้นความเร็ว (เหมือนที่ฉันทำ) เพราะทั้งหมดนี้เร็วมาก ๆ


ในทำนองเดียวกันในการทดสอบของฉันฉันพบว่าasการแปลง (โดยไม่ตรวจสอบข้อผิดพลาด) วิ่งเร็วกว่าการคัดเลือกประมาณ 1-3% (ประมาณ 540ms เทียบกับ 550ms ต่อ 100 ล้านครั้ง) จะไม่สร้างหรือทำลายแอปพลิเคชันของคุณ
palswim

1

นอกจากสิ่งที่ได้รับการเปิดเผยที่นี่แล้วฉันเพิ่งเจอความแตกต่างในทางปฏิบัติที่ฉันคิดว่าน่าสังเกตระหว่างการคัดเลือกนักแสดง

var x = (T) ...

กับการใช้asโอเปอเรเตอร์

นี่คือตัวอย่าง:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

Bottom line: GenericCaster2 จะไม่ทำงานกับประเภท struct GenericCaster จะ


1

หากคุณใช้ Office PIAs ที่กำหนดเป้าหมายเป็น. NET Framework 4.X คุณควรใช้คำหลักเป็นมิฉะนั้นจะไม่สามารถรวบรวมได้

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

การส่งเป็นปกติเมื่อกำหนดเป้าหมาย. NET 2.0:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

เมื่อกำหนดเป้าหมาย. NET 4.X ข้อผิดพลาดคือ:

ข้อผิดพลาด CS0656: สมาชิกคอมไพเลอร์ที่ขาดหายไปสมาชิก 'Microsoft.CSharp.RuntimeBinder.Binder.Convert'

ข้อผิดพลาด CS0656: สมาชิกคอมไพเลอร์ที่ขาดหายไปต้องการ 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'


0

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


ไม่เหมือนกันในขณะที่หนึ่งเรียก CastClass และสายอื่น ๆ IsInst ในรหัส IL
Jenix

0

สิ่งที่คุณเลือกอย่างยิ่งขึ้นอยู่กับสิ่งที่จำเป็น ฉันชอบการคัดเลือกนักแสดงที่ชัดเจน

IMyInterface = (IMyInterface)someobj;

เพราะถ้าวัตถุควรโดยประเภท IMyInterface และไม่ - มันเป็นปัญหาแน่นอน มันจะดีกว่าที่จะได้รับข้อผิดพลาดเร็วที่สุดเพราะข้อผิดพลาดที่แน่นอนจะได้รับการแก้ไขแทนการแก้ไขผลข้างเคียง

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


0

ขึ้นอยู่กับว่าคุณต้องการตรวจสอบโมฆะหลังจากใช้ "เป็น" หรือคุณต้องการให้แอปของคุณมีข้อยกเว้นหรือไม่?

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



0

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

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.