การเกิดชกมวยใน C #


86

ฉันพยายามรวบรวมสถานการณ์ทั้งหมดที่ชกมวยเกิดขึ้นใน C #:

  • การแปลงค่าเป็นSystem.Objectประเภท:

    struct S { }
    object box = new S();
    
  • การแปลงค่าเป็นSystem.ValueTypeประเภท:

    struct S { }
    System.ValueType box = new S();
    
  • การแปลงค่าของประเภทการแจงนับเป็นSystem.Enumประเภท:

    enum E { A }
    System.Enum box = E.A;
    
  • การแปลงประเภทค่าเป็นการอ้างอิงอินเทอร์เฟซ:

    interface I { }
    struct S : I { }
    I box = new S();
    
  • การใช้ประเภทค่าในการต่อสตริง C #:

    char c = F();
    string s1 = "char value will box" + c;
    

    หมายเหตุ:ค่าคงที่ของcharประเภทจะเชื่อมต่อกันในเวลาคอมไพล์

    หมายเหตุ:ตั้งแต่รุ่น 6.0 เรียบเรียง C # ปรับการเรียงต่อกันที่เกี่ยวข้องกับbool, char, IntPtr, UIntPtrประเภท

  • การสร้างผู้รับมอบสิทธิ์จากวิธีการอินสแตนซ์ชนิดค่า:

    struct S { public void M() {} }
    Action box = new S().M;
    
  • การเรียกใช้เมธอดเสมือนที่ไม่ถูกแทนที่บนชนิดค่า:

    enum E { A }
    E.A.GetHashCode();
    
  • การใช้รูปแบบคงที่ C # 7.0 ภายใต้isนิพจน์:

    int x = …;
    if (x is 42) { … } // boxes both 'x' and '42'!
    
  • การชกมวยในการแปลงประเภท C # tuple:

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • พารามิเตอร์ทางเลือกของobjectประเภทที่มีค่าเริ่มต้นประเภทค่า:

    void M([Optional, DefaultParameterValue(42)] object o);
    M(); // boxing at call-site
    
  • การตรวจสอบค่าของประเภททั่วไปที่ไม่มีข้อ จำกัด สำหรับnull:

    bool M<T>(T t) => t != null;
    string M<T>(T t) => t?.ToString(); // ?. checks for null
    M(42);
    

    หมายเหตุ:สิ่งนี้อาจได้รับการปรับให้เหมาะสมโดย JIT ในบางช่วงเวลา. NET

  • พิมพ์ค่าการทดสอบของประเภทที่ไม่ถูก จำกัด หรือstructประเภททั่วไปด้วยis/ asตัวดำเนินการ:

    bool M<T>(T t) => t is int;
    int? M<T>(T t) => t as int?;
    IEquatable<T> M<T>(T t) => t as IEquatable<T>;
    M(42);
    

    หมายเหตุ:สิ่งนี้อาจได้รับการปรับให้เหมาะสมโดย JIT ในบางช่วงเวลา. NET

มีสถานการณ์ของการชกมวยอีกหรือไม่ที่คุณอาจรู้


2
ฉันจัดการกับเรื่องนี้เมื่อไม่นานมานี้และพบว่าสิ่งนี้น่าสนใจทีเดียว: การตรวจจับ (ยกเลิก) การชกมวยโดยใช้ FxCop
George Duckett

วิธีการแปลงที่ดีมากที่คุณแสดง Infact ฉันไม่รู้จักอันที่ 2 หรืออาจจะไม่เคยลอง :) ขอบคุณ
Zenwalker

12
ควรเป็นคำถามวิกิชุมชน
เจ้าเล่ห์

2
ประเภทที่ว่างเปล่าล่ะ? private int? nullableInteger
allansson

1
@allansson ประเภทที่เป็นโมฆะเป็นเพียงชนิดของค่า
controlflow

คำตอบ:


42

นั่นเป็นคำถามที่ดีมาก!

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

ตัวอย่างเช่นเนื่องจากออบเจ็กต์เป็นประเภทอ้างอิงการแคสต์ประเภทค่าให้กับอ็อบเจ็กต์จึงต้องมีการอ้างอิงประเภทค่าซึ่งทำให้เกิดการชกมวย

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

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

ในช่วงหลายปีที่ผ่านมาฉันแนะนำให้นักพัฒนาซอฟต์แวร์จำเหตุผลเดียวของการชกมวย (ที่ฉันระบุไว้ข้างต้น) แทนที่จะจดจำทุกกรณีเนื่องจากรายการยาวและยากที่จะจำ นอกจากนี้ยังส่งเสริมความเข้าใจเกี่ยวกับรหัส IL ที่คอมไพลเลอร์สร้างขึ้นสำหรับรหัส C # ของเรา (ตัวอย่างเช่น + บนสตริงให้การเรียกใช้ String.Concat) เมื่อคุณสงสัยว่าคอมไพเลอร์สร้างอะไรขึ้นและหากเกิดการชกมวยขึ้นคุณสามารถใช้ IL Disassembler (ILDASM.exe) โดยทั่วไปคุณควรมองหารหัส opcode ของกล่อง (มีเพียงกรณีเดียวที่อาจเกิดการชกมวยแม้ว่า IL จะไม่รวม opcode ของกล่องก็ตามรายละเอียดเพิ่มเติมด้านล่าง)

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

อีกกรณีหนึ่งสำหรับการชกมวยที่ชัดเจนน้อยกว่าคือเมื่อเรียกใช้เมธอดคลาสพื้นฐานจากโครงสร้าง ตัวอย่าง:

struct MyValType
{
    public override string ToString()
    {
        return base.ToString();
    }
}

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

อย่างไรก็ตามสถานการณ์การชกมวยที่ไม่ชัดเจนทั้งสองนี้ยังมาจากกฎข้อเดียวข้างต้น เมื่อมีการเรียกใช้เมธอดบนคลาสพื้นฐานของประเภทค่าต้องมีบางอย่างสำหรับคีย์เวิร์ดนี้ที่จะอ้างถึง เนื่องจากคลาสพื้นฐานของประเภทค่าคือประเภทอ้างอิง (เสมอ) คีย์เวิร์ดนี้จึงต้องอ้างอิงถึงประเภทการอ้างอิงดังนั้นเราจึงต้องการการอ้างอิงถึงประเภทค่าดังนั้นการชกมวยจึงเกิดขึ้นเนื่องจากกฎข้อเดียว

นี่คือลิงค์โดยตรงไปยังส่วนของหลักสูตร. NET ออนไลน์ของฉันที่กล่าวถึงการชกมวยโดยละเอียด: http://motti.me/mq

หากคุณสนใจเฉพาะสถานการณ์การชกมวยขั้นสูงเพิ่มเติมนี่คือลิงค์โดยตรงที่นั่น (แม้ว่าลิงก์ด้านบนจะพาคุณไปที่นั่นด้วยเช่นกันเมื่อมีการกล่าวถึงสิ่งพื้นฐานเพิ่มเติม): http://motti.me/mu

ฉันหวังว่านี่จะช่วยได้!

มอตติ


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

@supercat การเรียกเมธอดใด ๆ ที่เรียกbaseแบบ value จะทำให้เกิดการชกมวย ซึ่งรวมถึงวิธีการเสมือนที่ไม่ได้ถูกแทนที่โดยโครงสร้างและObjectวิธีการที่ไม่เสมือนเลย (เช่นGetType()) ดูคำถามนี้
ŞafakGür

@ ŞafakGür: ฉันรู้ว่าผลสุดท้ายจะเป็นมวย ฉันสงสัยเกี่ยวกับกลไกที่แน่นอนที่มันเกิดขึ้น เนื่องจากคอมไพลเลอร์ที่สร้าง IL อาจไม่ทราบว่าประเภทนั้นเป็นประเภทค่าหรือการอ้างอิง (อาจเป็นแบบทั่วไป) จึงจะสร้าง callvirt โดยไม่คำนึงถึง JITter จะรู้ว่าประเภทนั้นเป็นประเภทค่าหรือไม่และจะแทนที่ ToString หรือไม่ดังนั้นจึงสามารถสร้างรหัสโทรไซต์เพื่อทำมวย หรืออาจสร้างโดยอัตโนมัติสำหรับทุกโครงสร้างที่ไม่ได้แทนที่ToStringmehtod public override void ToString() { return base.ToString(); }และ ...
supercat

... มีมวยเกิดขึ้นภายในวิธีการนั้น เนื่องจากวิธีการนี้จะสั้นมากจึงสามารถเรียงต่อกันได้ การทำสิ่งต่างๆด้วยวิธีการหลังจะทำให้ToString()สามารถเข้าถึงเมธอดของ struct ผ่าน Reflection ได้เช่นเดียวกับวิธีอื่น ๆ และใช้ในการสร้างตัวแทนแบบคงที่ซึ่งใช้ชนิดของโครงสร้างเป็นrefพารามิเตอร์ [สิ่งนี้ใช้ได้กับวิธีการโครงสร้างที่ไม่ได้รับการสืบทอด] แต่ฉัน เพิ่งลองสร้างผู้รับมอบสิทธิ์ดังกล่าว แต่ไม่ได้ผล เป็นไปได้ไหมที่จะสร้างตัวแทนแบบคงที่สำหรับToString()เมธอดของโครงสร้างและถ้าเป็นเช่นนั้นประเภทพารามิเตอร์ควรเป็นอย่างไร
supercat

ลิงค์เสีย
OfirD

5

การเรียกใช้วิธี GetType () ที่ไม่ใช่เสมือนในประเภทค่า:

struct S { };
S s = new S();
s.GetType();

2
GetTypeจำเป็นต้องมีการชกมวยไม่ใช่เพียงเพราะไม่ใช่เสมือน แต่เป็นเพราะสถานที่จัดเก็บประเภทค่าซึ่งแตกต่างจากวัตถุฮีปไม่มีฟิลด์ "ซ่อน" ซึ่งGetType()สามารถใช้ระบุประเภทได้
supercat

@supercat อืมมม 1. Boxing เพิ่มโดยคอมไพเลอร์และฟิลด์ที่ซ่อนอยู่ที่ใช้โดยรันไทม์ อาจเป็นคอมไพเลอร์เพิ่ม Boxing เพราะมันรู้เกี่ยวกับรันไทม์… 2. เมื่อเราเรียก ToString ที่ไม่ใช่เสมือน (สตริง) บนค่า enum มันต้องใช้การชกมวยด้วยและฉันไม่เชื่อว่าคอมไพเลอร์เพิ่มสิ่งนี้เพราะรู้รายละเอียดการใช้งาน Enum ToString (สตริง) . ดังนั้นฉันคิดว่าฉันสามารถพูดได้ว่าการชกมวยเกิดขึ้นเสมอเมื่อวิธีที่ไม่ใช่เสมือนใน "ประเภทค่าฐาน" เรียกว่า
Viacheslav Ivanov

ฉันไม่ได้คิดว่าEnumจะมีวิธีการที่ไม่ใช่เสมือนของตัวเองแม้ว่าToString()วิธีการEnumนั้นจะต้องมีการเข้าถึงข้อมูลประเภท ฉันสงสัยว่าObject, ValueTypeหรือEnumมีวิธีการที่ไม่ใช่เสมือนใด ๆ ที่สามารถดำเนินการงานของพวกเขาโดยไม่มีข้อมูลประเภท
supercat

3

กล่าวถึงในคำตอบของ Motti เพียงแค่แสดงด้วยตัวอย่างโค้ด:

พารามิเตอร์ที่เกี่ยวข้อง

public void Bla(object obj)
{

}

Bla(valueType)

public void Bla(IBla i) //where IBla is interface
{

}

Bla(valueType)

แต่ปลอดภัย:

public void Bla<T>(T obj) where T : IBla
{

}

Bla(valueType)

ประเภทผลตอบแทน

public object Bla()
{
    return 1;
}

public IBla Bla() //IBla is an interface that 1 inherits
{
    return 1;
}

กำลังตรวจสอบ T ที่ไม่มีข้อ จำกัด กับ null

public void Bla<T>(T obj)
{
    if (obj == null) //boxes.
}

การใช้ไดนามิก

dynamic x = 42; (boxes)

อีกอัน

enumValue.HasFlag


0
  • การใช้คอลเลกชันที่ไม่ได้ทั่วไปในSystem.Collectionsเช่น หรือArrayListHashTable

จริงอยู่ที่นี่เป็นกรณีเฉพาะของกรณีแรกของคุณ แต่อาจเป็น gotcha ที่ซ่อนอยู่ มันน่าพิศวงจำนวนของรหัสที่ฉันยังคงเจอในวันนี้ว่าใช้เหล่านี้แทนและList<T>Dictionary<TKey,TValue>


0

การเพิ่มค่าประเภทค่าใด ๆ ลงใน ArrayList ทำให้เกิดการชกมวย:

ArrayList items = ...
numbers.Add(1); // boxing to object
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.