Quyền anh xảy ra trong C #


85

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

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

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

    struct S { }
    System.ValueType box = new S();
    
  • Chuyển đổi giá trị của kiểu liệt kê thành System.Enumkiể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 nối chuỗi C #:

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

    lưu ý: các hằng charkiểu được nối với nhau tại thời điểm biên dịch

    lưu ý: kể từ phiên bản 6.0 biên dịch C # Tối ưu hoá nối liên quan bool, char, IntPtr, UIntPtrloại

  • Tạo ủy quyền 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 bị ghi đè trên các loại giá trị:

    enum E { A }
    E.A.GetHashCode();
    
  • Sử dụng các mẫu hằng số C # 7.0 trong isbiểu thức:

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

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • Các tham số tùy chọn của objectloại với các giá trị mặc định của 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 chung không bị giới hạn cho null:

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

    lưu ý: đ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

  • Loại giá trị kiểm tra của loại không bị hạn chế hoặc structloại chung với is/ astoá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);
    

    lưu ý: đ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òn những tình huống đấm bốc nào nữa, có thể ẩn mà bạn biết không?


2
Tôi xử lý này một số thời gian trước đây, và thấy khá thú vị này: Phát hiện (un) đấm bốc sử dụng FxCop
George Duckett

Những cách chuyển đổi rất tốt mà bạn đã thể hiện. Infact tôi đã không biết một trong 2 hoặc có lẽ chưa bao giờ thử nó :) Cảm ơn
Zenwalker

12
nó phải là câu hỏi wiki cộng đồng
Sly

2
Còn các kiểu nullable thì sao? private int? nullableInteger
allansson

1
@allansson, các kiểu nullable chỉ là các kiểu giá trị
controlflow

Câu trả lời:


42

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

Quyền anh xảy ra chính xác vì một lý do: 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 nằm trong quy tắc này.

Ví dụ: vì đối tượng là một kiểu tham chiếu, việc truyền một kiểu giá trị cho đối tượng yêu cầu một tham chiếu đến một kiểu giá trị, điều này gây ra quyền anh.

Nếu bạn muốn liệt kê mọi trường hợp có thể xảy ra, bạn cũng nên bao gồm các dẫn xuất, chẳng hạn như trả về 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 tự động chuyển kiểu giá trị cho đối tượng / giao diện.

Nhân tiện, trường hợp nối chuỗi mà bạn đã xác định một cách sắc sảo cũng bắt nguồn từ việc đúc thành đối tượng. Toán tử + được trình biên dịch dịch thành một lệnh gọi đến phương thức Concat của chuỗi, phương thức này chấp nhận một đối tượng cho kiểu giá trị mà bạn truyền vào, vì vậy việc truyền sang đối tượng và do đó xảy ra quá trình đóng hộp.

Trong nhiều năm, 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ì ghi nhớ từng trường hợp đơn lẻ, bởi vì danh sách dài và khó nhớ. Điều này cũng thúc đẩy sự hiểu biết về mã IL mà trình biên dịch tạo ra cho mã C # của chúng ta (ví dụ: + trên chuỗi mang lại lời gọi đến String.Concat). Khi bạn nghi ngờ những gì trình biên dịch tạo ra và nếu quyền anh xảy ra, bạn có thể sử dụng IL Disassembler (ILDASM.exe). Thông thường, bạn nên tìm mã chọn hộp (chỉ có một trường hợp khi quyền anh có thể xảy ra mặc dù IL không bao gồm mã hộp, chi tiết hơn bên dưới).

Nhưng tôi đồng ý rằng một số trận quyền anh ít rõ ràng hơn. Bạn đã liệt kê một trong số chúng: gọi một phương thức không bị 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 hộp opcode mà là opcode ràng buộc, vì vậy ngay cả trong IL cũng không rõ ràng là quyền anh xảy ra! Tôi sẽ không đi vào chi tiết chính xác tại sao để ngăn câu trả lời này trở nên dài hơn ...

Một trường hợp khác đối với quyền anh í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 bị ghi đè, vì vậy việc gọi ToString trên MyValType sẽ không tạo ra quyền anh. Tuy nhiên, việc thực hiện gọi ToString cơ sở và điều đó gây ra quyền anh (kiểm tra IL!).

Nhân tiện, hai kịch bản quyền anh không rõ ràng này cũng bắt nguồn từ mộ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ì đó để từ khóa this tham chiếu đến. Vì lớp cơ sở của một kiểu giá trị là (luôn luôn) là một kiểu tham chiếu, từ khóa this phải tham chiếu đến một kiểu tham chiếu, và do đó chúng ta cần một tham chiếu đến một kiểu giá trị và do đó, quyền chọn xảy ra do một quy tắc.

Đây là liên kết trực tiếp đến phần 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 các kịch bản quyền anh nâng cao hơn thì đây là một liên kết trực tiếp ở đó (mặc dù liên kết ở trên cũng sẽ đưa bạn đến đó 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


1
Nếu một ToString()được gọi trên một loại giá trị cụ thể không ghi đè lên nó, thì loại giá trị có được đóng hộp tại trang web gọi hay không, hay phương thức sẽ được gửi (không có quyền chọn) đến một ghi đè được tạo tự động không thực hiện gì ngoài chuỗi (với quyền anh) sang phương pháp cơ sở?
supercat

@supercat Gọi bất kỳ phương thức nào gọi basekiểu giá trị sẽ gây ra hiện tượng quyền anh. Điều này bao gồm các phương thức ảo không bị cấu trúc ghi đè và các Objectphương thức hoàn toàn không ảo (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à quyền anh. Tôi đã tự hỏi về cơ chế chính xác mà nó xảy ra. Vì trình biên dịch tạo IL có thể không biết liệu kiểu đó là kiểu giá trị hay tham chiếu (nó có thể là chung) nên nó sẽ tạo ra một callvirt bất kể. JITter sẽ biết liệu kiểu có phải là 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 mã trang web gọi để thực hiện quyền truy cập; cách khác, nó có thể tự động tạo cho mọi cấu trúc không ghi đè lên ToStringmehtod public override void ToString() { return base.ToString(); }và ...
supercat 14/02

... có quyền anh xảy ra trong phương pháp đó. Vì phương thức này sẽ rất ngắn, nên nó có thể được xếp vào hàng. Làm những việc với cách tiếp cận thứ hai sẽ cho phép ToString()phương thức của struct đượ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 lấy kiểu struct làm reftham số [một thứ 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 đại biểu như vậy và nó không hoạt động. Có thể tạo một ủy nhiệm tĩnh cho ToString()phương thức của struct không và nếu có thì kiểu tham số phải là gì?
supercat

Các liên kết bị hỏng.
OfirD

5

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

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

2
GetTypeyêu cầu quyền anh không chỉ vì nó không ảo, mà bởi vì các vị trí lưu trữ kiểu giá trị, không giống như các đối tượng đống, không có trường "ẩn" GetType()có thể sử dụng để xác định loại của chúng.
supercat

@supercat Hừm. 1. Quyền anh được thêm bởi trình biên dịch và trường ẩn được sử dụng trong thời gian chạy. Có thể 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 (chuỗi) không ảo trên giá trị enum, nó cũng yêu cầu quyền anh và tôi không tin rằng trình biên dịch thêm điều này vì nó biết về chi tiết triển khai Enum.ToString (chuỗi) . Vì vậy, tôi nghĩ rằng, tôi có thể nói rằng quyền anh luôn luôn xảy ra khi phương pháp không ảo trên "loại giá trị cơ sở" được gọi.
Viacheslav Ivanov

Tôi đã không xem xét Enumcó bất kỳ phương thức không ảo nào của riêng nó, mặc dù một ToString()phương thức cho một Enumsẽ cần có quyền truy cập vào thông tin loại. Tôi tự hỏi liệu Object, ValueTypehoặc Enumcó bất kỳ phương thức không ả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 không.
supercat

3

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

Các thông 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ả lại

public object Bla()
{
    return 1;
}

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

Kiểm tra T không bị ràng buộc so với null

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

Sử dụng động

dynamic x = 42; (boxes)

Cái khác

enumValue.HasFlag


0
  • Sử dụng các bộ sưu tập không chung chung trong System.Collectionschẳng hạn như ArrayListhoặc HashTable.

Được cho là đây là các 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ể được ẩn. Thật ngạc nhiên khi số lượng mã mà tôi vẫn gặp ngày nay sử dụng chúng thay vì List<T>Dictionary<TKey,TValue>.


0

Việc thêm bất kỳ giá trị kiểu giá trị nào vào ArrayList sẽ gây ra hiện tượng quyền anh:

ArrayList items = ...
numbers.Add(1); // boxing to object
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.