a

Câu hỏi Trong C # /. NET tại sao sbyte [] giống như byte [] ngoại trừ việc nó không?


Tôi chỉ quan sát thấy một hiện tượng kỳ lạ trong C # /. NET.

Tôi đã tạo ví dụ tối thiểu này để minh họa:

if (new sbyte[5] is byte[])
{
 throw new ApplicationException("Impossible!");
}

object o = new sbyte[5];

if (o is byte[])
{
 throw new ApplicationException("Why???");
}

Điều này sẽ ném "Tại sao ???", nhưng không phải "Không thể!". Nó hoạt động cho tất cả các mảng của các loại tích phân có cùng kích thước. Ai đó có thể giải thích điều này với tôi? Tôi bối rối. Tôi đang sử dụng .NET 4 bằng cách này.

Tái bút: Tôi biết rằng tôi có thể nhận được kết quả mong đợi bằng cách sử dụng o.GetType() == typeof(byte[]).


51
2017-08-15 20:44


gốc




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


Các quy tắc đúc CLR xác định rằng điều này là có thể. Các quy tắc C # nói rằng điều đó là không thể. Nhóm C # đã quyết định một cách có ý thức rằng họ sẽ chịu đựng độ lệch này từ spec vì nhiều lý do khác nhau.

Tại sao CLR cho phép điều này? Có lẽ vì họ có thể thực hiện nó một cách thuận tiện. byte và sbyte có cùng biểu diễn nhị phân để bạn có thể "xử lý" byte[] như một sbyte[]  mà không vi phạm an toàn bộ nhớ.

Thủ thuật tương tự cũng hoạt động với các kiểu nguyên thủy khác có cùng bố cục bộ nhớ.


51
2017-08-15 20:49



Bạn nên bao gồm thực tế là độ lệch này bị cô lập để đánh giá các mảng đã nhập. - Michael Graczyk
Nó hoạt động cho các loại ref, quá (array co / contravariance). Mặc định, các mảng được nhập có kiểm tra thời gian chạy. Tôi trích dẫn Eric: Một trong những quy tắc của CLI là "nếu X được gán tương ứng với Y thì X [] là nhiệm vụ tương thích với Y []". - usr
Độ lệch chỉ dành cho các loại giá trị. Thay thế sbyte với stringvà byte với 'đối tượng'. Các mã ném tại "Impossible!". - Michael Graczyk
BTW: tại sao ví dụ: Array.Copy ném một ArrayTypeMismatchException khi tôi cố sao chép từ (byte[])o thật byte[] nếu các quy tắc CLR nói rằng diễn viên có thể? Tôi không nghĩ rằng việc thực hiện Array là C # cụ thể ... - user1047771
Tôi nghĩ rằng toàn bộ toolchain của Microsoft là không phù hợp xung quanh vấn đề này bởi vì nó có tầm nhìn rất ít. Mặc dù vậy, không quan trọng lắm trong thực tế. - usr


Buồn cười, tôi bị cắn bởi câu hỏi của mình, Tại sao Linq Cast này thất bại khi sử dụng ToList?

Jon Skeet (Tất nhiên) giải thích rằng vấn đề của tôi là trình biên dịch C #, vì lý do gì đó, nghĩ rằng chúng không bao giờ có thể giống nhau, và giúp tối ưu hóa nó một cách hữu ích. Tuy nhiên, CLR làm để điều này xảy ra. Các diễn viên để đối tượng ném ra tối ưu hóa trình biên dịch, vì vậy nó đi qua CLR.

Phần liên quan từ câu trả lời của anh ấy:

Mặc dù trong C # bạn không thể cast một byte [] vào một sbyte [] trực tiếp, CLR cho phép nó:

var foo = new byte[] {246, 127};
// This produces a warning at compile-time, and the C# compiler "optimizes"
// to the constant "false"
Console.WriteLine(foo is sbyte[]);

object x = foo;
// Using object fools the C# compiler into really consulting the CLR... which
// allows the conversion, so this prints True
Console.WriteLine(x is sbyte[]);

Joel hỏi một câu hỏi thú vị trong các bình luận, "Hành vi này có được kiểm soát bởi cờ Mã Tối ưu hóa hay không (/o đến trình biên dịch)? "

Với mã này:

static void Main(string[] args)
{
    sbyte[] baz = new sbyte[0];
    Console.WriteLine(baz is byte[]);
}

Và được biên dịch với csc /o- Code.cs (không tối ưu hóa), có vẻ như trình biên dịch tối ưu hóa nó. Kết quả IL:

IL_0000:  nop
IL_0001:  ldc.i4.0
IL_0002:  newarr     [mscorlib]System.SByte
IL_0007:  stloc.0
IL_0008:  ldc.i4.0
IL_0009:  call       void [mscorlib]System.Console::WriteLine(bool)
IL_000e:  nop
IL_000f:  ret

IL_0008 tải 0 (sai) trực tiếp lên ngăn xếp, sau đó gọi WriteLine trên IL_0009. Vì vậy, không, cờ tối ưu hóa không tạo nên sự khác biệt. Nếu CLR được tư vấn, isinst hướng dẫn sẽ được sử dụng. Nó có lẽ sẽ giống như thế này bắt đầu từ IL_0008:

IL_0008:  ldloc.0
IL_0009:  isinst     uint8[]
IL_000e:  ldnull
IL_000f:  cgt.un
IL_0011:  call       void [mscorlib]System.Console::WriteLine(bool)

Tôi sẽ đồng ý với hành vi của trình tối ưu hóa. Cờ tối ưu hoá không được thay đổi hành vi của chương trình của bạn.


28
2017-08-15 20:51



Điều này có nghĩa là bằng cách thay đổi cài đặt trình biên dịch, bạn có thể nhận được phần "tối ưu hóa thành sai" không xảy ra? - Joel Rondeau
Câu trả lời được chấp nhận không cố gắng giải thích tại sao hai câu trả lời được cho là giống hệt nhau nếu các câu lệnh hoạt động khác nhau như câu lệnh này. - Chad Schouggins
@ JoelRondeau Câu hỏi thú vị. Đã cập nhật câu trả lời. - vcsjones


VB.NET thực sự "ném" tại biên dịch thời gian:

Biểu thức của loại 'mảng 1 chiều của SByte' không bao giờ có thể thuộc loại 'mảng 1 chiều' của Byte '.

tương đương với cái đầu tiên if tuyên bố.

Và tương đương với giây if thành công (tức là nó ném ngoại lệ được mã hóa) vào thời gian chạy như mong đợi vì nó là cùng một CLR.


2
2017-08-22 06:47



Hấp dẫn. C # sẽ phát ra một cảnh báo, vì vậy có thể nếu bạn sử dụng "xử lý cảnh báo dưới dạng lỗi" trong C # thì bạn có thể nhận được cùng một hành vi. - vcsjones


Dưới đây là một ví dụ đơn giản cho thấy cùng một vấn đề:

static void Main(string[] args)
{
    bool a = ((object) new byte[0]) is sbyte[];
    bool b = (new byte[0]) is sbyte[];

    Console.WriteLine(a == b); // False
}

Sự mâu thuẫn phát sinh bởi vì trình biên dịch C # quyết định rằng nó biết kết quả của (new byte[0]) is sbyte[] tại thời gian biên dịch và chỉ thay thế false. Có lẽ nó thực sự nên thay thế true, để phù hợp hơn với hành vi CLR.

Theo như tôi có thể nói, nó chỉ tối ưu hóa nhỏ này không phù hợp. Nó chỉ xảy ra khi cả hai bên của isbiểu thức được gõ tĩnh như một mảng có kiểu phần tử là một số nguyên đã ký hoặc không dấu hoặc một enum và các kích thước của các số nguyên giống nhau.

Tin tốt là trong khi điều này có vẻ không nhất quán, C # sẽ luôn đưa ra cảnh báo khi nó thay thế false thành những biểu hiện như vậy - trong thực tế, tôi nghĩ rằng điều này có thể hữu ích hơn là trở về lặng lẽ true.


1
2017-09-25 14:48