Câu hỏi Boxing xuất hiện trong C #


Tôi đang cố gắng thu thập tất cả các tình huống trong đó boxing xảy ra trong C #:

  • Chuyển đổi loại giá trị thành System.Object kiểu:

    struct S { }
    object box = new S();
    
  • Chuyển đổi loại giá trị thành System.ValueType kiểu:

    struct S { }
    System.ValueType box = new S();
    
  • Chuyển đổi giá trị của kiểu liệt kê thành System.Enum kiểu:

    enum E { A }
    System.Enum box = E.A;
    
  • Chuyển đổi loại giá trị thành tham chiếu giao diện:

    interface I { }
    struct S : I { }
    I box = new S();
    
  • Sử dụng các kiểu giá trị trong chuỗi nối chuỗi C #:

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

    chú thích: hằng số của char loại được nối vào thời gian biên dịch

    chú thích: kể từ phiên bản 6.0 C # trình biên dịch tối ưu hóa nối liên quan bool, char, IntPtr, UIntPtr các loại

  • Tạo đại biểu từ phương thức thể hiện kiểu giá trị:

    struct S { public void M() {} }
    Action box = new S().M;
    
  • Gọi các phương thức ảo không ghi đè trên các loại giá trị:

    enum E { A }
    E.A.GetHashCode();
    
  • Sử dụng các mẫu liên tục C # 7.0 trong is biểu hiện:

    int x = …;
    if (x is 42) { … } // boxes both 'x' and '42'!
    
  • Quyền anh trong chuyển đổi loại C # tuple:

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • Các thông số tùy chọn của object nhập với giá trị mặc định loại giá trị:

    void M([Optional, DefaultParameterValue(42)] object o);
    M(); // boxing at call-site
    
  • Kiểm tra giá trị của loại generic không bị ràng buộc cho null:

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

    chú thích: điều này có thể được tối ưu hóa bởi JIT trong một số thời gian chạy .NET.

  • Nhập giá trị thử nghiệm không bị giới hạn hoặc struct loại chung với is/as toán tử:

    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);
    

    chú thích: điều này có thể được tối ưu hóa bởi JIT trong một số thời gian chạy .NET.

Có bất kỳ tình huống nào khác của boxing, có thể bị che giấu, mà bạn biết không?


76
2017-11-03 13:22


gốc


Tôi xử lý điều này một thời gian trước đây, và thấy điều này khá thú vị: Phát hiện (un) boxing sử dụng FxCop - George Duckett
Chuyển đổi rất đẹp theo cách bạn đã hiển thị. Infact tôi không biết thứ hai hoặc có lẽ không bao giờ thử nó :) Cảm ơn - Zenwalker
nó nên là câu hỏi về cộng đồng wiki - Sly
Điều gì về các loại nullable? private int? nullableInteger - allansson
@allansson, các loại nullable chỉ là loại giá trị - ControlFlow


Các câu trả lời:


Đó là một câu hỏi tuyệt vời!

Boxing xảy ra vì một lý do chính xác: khi chúng ta cần tham chiếu đến một loại giá trị. Mọi thứ bạn liệt kê đều rơi vào quy tắc này.

Ví dụ kể từ khi đối tượng là một kiểu tham chiếu, việc đúc một kiểu giá trị cho đối tượng đòi hỏi một tham chiếu đến một kiểu giá trị, gây ra boxing.

Nếu bạn muốn liệt kê tất cả các kịch bản có thể, bạn cũng nên bao gồm các dẫn xuất, chẳng hạn như trả về một kiểu giá trị từ một phương thức trả về đối tượng hoặc kiểu giao diện, vì điều này sẽ tự động tạo kiểu giá trị cho đối tượng / giao diện.

Bằng cách này, trường hợp nối chuỗi bạn xác định rõ ràng cũng có nguồn gốc từ đúc đến đối tượng. Toán tử + được dịch bởi trình biên dịch để gọi tới phương thức Concat của chuỗi, chấp nhận một đối tượng cho kiểu giá trị mà bạn vượt qua, do đó hãy truyền tới đối tượng và do đó việc xảy ra boxing.

Trong những năm qua, tôi luôn khuyên các nhà phát triển nên nhớ lý do duy nhất cho quyền anh (tôi đã nêu ở trên) thay vì phải ghi nhớ từng trường hợp, vì danh sách dài và khó nhớ. Điều này cũng thúc đẩy sự hiểu biết về những gì mã IL trình biên dịch tạo ra cho mã C # của chúng tôi (ví dụ + trên chuỗi mang lại một cuộc gọi đến String.Concat). Khi bạn nghi ngờ trình biên dịch tạo ra gì và nếu xảy ra boxing, bạn có thể sử dụng IL Disassembler (ILDASM.exe). Thông thường, bạn nên tìm kiếm các opcode hộp (chỉ có một trường hợp khi boxing có thể xảy ra mặc dù IL không bao gồm các opcode hộp, chi tiết hơn dưới đây).

Nhưng tôi đồng ý rằng một số lần xuất hiện boxing không rõ ràng. Bạn đã liệt kê một trong số chúng: gọi một phương thức không được ghi đè của một loại giá trị. Trên thực tế, điều này ít rõ ràng hơn vì một lý do khác: khi bạn kiểm tra mã IL, bạn không thấy mã opcode của hộp, mà là mã opcode hạn chế, vì vậy ngay cả trong IL, không rõ là boxing xảy ra! Tôi sẽ không đi vào chi tiết chính xác tại sao để ngăn chặn câu trả lời này trở nên dài hơn ...

Một trường hợp khác cho boxing ít rõ ràng hơn là khi gọi một phương thức lớp cơ sở từ một cấu trúc. Thí dụ:

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

Ở đây ToString được ghi đè, do đó, việc gọi ToString trên MyValType sẽ không tạo quyền. Tuy nhiên, việc thực hiện gọi ToString cơ sở và gây ra boxing (kiểm tra IL!).

Nhân tiện, hai kịch bản boxing không rõ ràng này cũng xuất phát từ quy tắc duy nhất ở trên. Khi một phương thức được gọi trên lớp cơ sở của một kiểu giá trị, phải có một cái gì đó cho điều này từ khóa để tham khảo. Vì lớp cơ sở của một loại giá trị là (luôn luôn) một kiểu tham chiếu, điều này từ khóa phải tham chiếu đến một loại tham chiếu, và vì vậy chúng ta cần tham chiếu đến một loại giá trị và do đó boxing xảy ra do quy tắc duy nhất.

Đây là một liên kết trực tiếp đến phần của khóa học .NET trực tuyến của tôi, thảo luận chi tiết về quyền anh: http://motti.me/mq

Nếu bạn chỉ quan tâm đến kịch bản boxing cao cấp hơn ở đây là một liên kết trực tiếp ở đó (mặc dù liên kết ở trên sẽ đưa bạn đến đó một khi nó thảo luận về những thứ cơ bản hơn): http://motti.me/mu

Tôi hi vọng cái này giúp được!

Motti


40
2017-11-03 15:32



Nếu một ToString() được gọi trên loại giá trị cụ thể không ghi đè lên, loại giá trị sẽ được đóng hộp tại trang web cuộc gọi hoặc phương thức sẽ được gửi đi (không có quyền anh) đến ghi đè được tạo tự động mà không có gì ngoài chuỗi (với boxing) phương pháp cơ sở? - supercat
@supercat Gọi bất kỳ phương thức nào có cuộc gọi base trong một loại giá trị sẽ gây ra quyền anh. Điều này bao gồm các phương thức ảo không bị ghi đè bởi cấu trúc và Object phương pháp không ảo ở tất cả (như GetType()). Xem câu hỏi này. - Şafak Gür
@ ŞafakGür: Tôi biết kết quả cuối cùng sẽ là đấm bốc. Tôi đã tự hỏi về cơ chế chính xác mà qua đó nó xảy ra. Kể từ khi trình biên dịch tạo ra IL có thể không biết liệu loại là một loại giá trị hoặc tham chiếu (nó có thể là chung chung) nó sẽ tạo ra một callvirt bất kể. JITter sẽ biết liệu loại đó có phải là một kiểu giá trị hay không và liệu nó có ghi đè ToString hay không, vì vậy nó có thể tạo ra mã gọi trang để thực hiện việc boxing; cách khác, nó có thể tự động tạo cho mọi cấu trúc không ghi đè ToStringmột mehtod public override void ToString() { return base.ToString(); } và ... - supercat
... có boxing xảy ra trong phương pháp đó. Vì phương pháp này sẽ rất ngắn, nó có thể được xếp hàng. Làm những việc với cách tiếp cận thứ hai sẽ cho phép cấu trúc ToString() phương thức được truy cập thông qua Reflection giống như bất kỳ phương thức nào khác và được sử dụng để tạo một ủy nhiệm tĩnh có kiểu cấu trúc như là một ref tham số [một điều như vậy hoạt động với các phương thức struct không kế thừa], nhưng tôi vừa thử tạo một ủy nhiệm như vậy và nó không hoạt động. Có thể tạo ra một đại biểu tĩnh cho một cấu trúc của ToString() và nếu có thì kiểu tham số sẽ là gì? - supercat
Liên kết bị hỏng. - HeyJude


Gọi phương thức GetType () không phải ảo trên loại giá trị:

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

5
2017-11-03 15:20



GetType yêu cầu quyền anh không chỉ vì nó không phải ảo, mà là vì các vị trí lưu trữ kiểu giá trị, không giống như các đối tượng heap, không có trường "ẩn" GetType() có thể sử dụng để xác định loại của họ. - supercat
@supercat Hmmm. 1. Boxing được thêm vào bởi trình biên dịch và trường ẩn được sử dụng theo thời gian chạy. Có thể được trình biên dịch thêm quyền anh vì nó biết về thời gian chạy… 2. Khi chúng ta gọi ToString không phải là chuỗi (on) trên giá trị enum nó cũng đòi hỏi quyền anh và tôi không tin rằng trình biên dịch thêm điều này bởi vì nó biết về Enum.ToString (chuỗi) . Vì vậy, tôi nghĩ rằng, tôi có thể nói rằng boxing luôn luôn xảy ra khi phương pháp phi ảo trên "loại giá trị cơ sở" được gọi là. - Viacheslav Ivanov
Tôi đã không xem xét Enum có bất kỳ phương pháp phi ảo nào, mặc dù ToString() phương pháp cho một Enum sẽ cần phải có quyền truy cập vào thông tin loại. Tôi tự hỏi, nếu Object, ValueType, hoặc là Enum có bất kỳ phương pháp phi ảo nào có thể thực hiện công việc của họ mà không có thông tin loại. - supercat


Được đề cập trong câu trả lời của Motti, chỉ minh họa bằng các mẫu mã:

Tham số liên quan

public void Bla(object obj)
{

}

Bla(valueType)

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

}

Bla(valueType)

Nhưng điều này là an toàn:

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

}

Bla(valueType)

Loại trả về

public object Bla()
{
    return 1;
}

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

Kiểm tra T không bị giới hạn đối với null

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

Sử dụng năng động

dynamic x = 42; (boxes)

Một số khác

enumValue.HasFlag


1
2018-04-16 09:49





  • Sử dụng các bộ sưu tập không chung chung trong System.Collections nhu la ArrayList hoặc là HashTable.

Cấp đây là những trường hợp cụ thể của trường hợp đầu tiên của bạn, nhưng chúng có thể bị ẩn đi. Thật tuyệt vời khi tôi sử dụng số lượng mã này thay vì List<T> và Dictionary<TKey,TValue>.


0
2017-11-03 15:40





Việc thêm bất kỳ giá trị kiểu giá trị nào vào ArrayList gây ra boxing:

ArrayList items = ...
numbers.Add(1); // boxing to object

0
2017-11-03 21:51