Định nghĩa của Toán tử == Toán tử cho Double


126

Vì một số lý do, tôi đã lẻn vào nguồn .NET Framework cho lớp Doublevà phát hiện ra rằng khai báo ==là:

public static bool operator ==(Double left, Double right) {
    return left == right;
}

Logic tương tự áp dụng cho mọi toán tử.


  • Điểm của định nghĩa như vậy là gì?
  • Làm thế nào nó hoạt động?
  • Tại sao nó không tạo ra một đệ quy vô hạn?

17
Tôi đang mong đợi một đệ quy vô tận.
HimBromBeere

5
Tôi khá chắc chắn rằng nó không được sử dụng để so sánh bất cứ nơi nào với gấp đôi, thay vào đó ceqđược ban hành trong IL. Đây chỉ là ở đó để điền vào một số mục đích tài liệu, mặc dù không thể tìm thấy nguồn.
Habib

2
Nhiều khả năng để toán tử này có thể thu được thông qua Reflection.
Damien_The_Unbeliever

3
Điều đó sẽ không bao giờ được gọi, trình biên dịch có logic đẳng thức được nướng trong (ceq opcode) xem Khi nào toán tử Double's == được gọi?
Alex K.

1
@ZoharPeled chia đôi với 0 là hợp lệ và sẽ dẫn đến vô cực dương hoặc âm.
Magnus

Câu trả lời:


62

Trong thực tế, trình biên dịch sẽ biến ==toán tử thành ceqmã IL và toán tử mà bạn đề cập sẽ không được gọi.

Lý do cho toán tử trong mã nguồn có khả năng vì vậy nó có thể được gọi từ các ngôn ngữ khác ngoài C # không dịch CEQtrực tiếp thành cuộc gọi (hoặc thông qua phản xạ). Mã trong toán tử sẽ được biên dịch thành a CEQ, do đó không có đệ quy vô hạn.

Trong thực tế, nếu bạn gọi toán tử thông qua phản xạ, bạn có thể thấy toán tử đó được gọi (chứ không phải là một CEQlệnh) và rõ ràng là không đệ quy vô hạn (vì chương trình kết thúc như mong đợi):

double d1 = 1.1;
double d2 = 2.2;

MethodInfo mi = typeof(Double).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public );

bool b = (bool)(mi.Invoke(null, new object[] {d1,d2}));

Kết quả IL (được biên soạn bởi LinqPad 4):

IL_0000:  nop         
IL_0001:  ldc.r8      9A 99 99 99 99 99 F1 3F 
IL_000A:  stloc.0     // d1
IL_000B:  ldc.r8      9A 99 99 99 99 99 01 40 
IL_0014:  stloc.1     // d2
IL_0015:  ldtoken     System.Double
IL_001A:  call        System.Type.GetTypeFromHandle
IL_001F:  ldstr       "op_Equality"
IL_0024:  ldc.i4.s    18 
IL_0026:  call        System.Type.GetMethod
IL_002B:  stloc.2     // mi
IL_002C:  ldloc.2     // mi
IL_002D:  ldnull      
IL_002E:  ldc.i4.2    
IL_002F:  newarr      System.Object
IL_0034:  stloc.s     04 // CS$0$0000
IL_0036:  ldloc.s     04 // CS$0$0000
IL_0038:  ldc.i4.0    
IL_0039:  ldloc.0     // d1
IL_003A:  box         System.Double
IL_003F:  stelem.ref  
IL_0040:  ldloc.s     04 // CS$0$0000
IL_0042:  ldc.i4.1    
IL_0043:  ldloc.1     // d2
IL_0044:  box         System.Double
IL_0049:  stelem.ref  
IL_004A:  ldloc.s     04 // CS$0$0000
IL_004C:  callvirt    System.Reflection.MethodBase.Invoke
IL_0051:  unbox.any   System.Boolean
IL_0056:  stloc.3     // b
IL_0057:  ret 

Điều thú vị - các nhà khai thác cùng không tồn tại (hoặc trong các nguồn tài liệu tham khảo hoặc thông qua phản ánh) với nhiều loại không thể thiếu, chỉ Single, Double, Decimal, String, và DateTime, trong đó đã bác bỏ lý thuyết của tôi rằng họ tồn tại để được gọi từ các ngôn ngữ khác. Rõ ràng bạn có thể đánh đồng hai số nguyên trong các ngôn ngữ khác mà không cần các toán tử này, vì vậy chúng tôi quay lại câu hỏi "tại sao chúng tồn tại cho double"?


12
Vấn đề duy nhất tôi có thể thấy với điều này là đặc tả ngôn ngữ C # nói rằng các toán tử quá tải được ưu tiên hơn các toán tử tích hợp. Vì vậy, chắc chắn, một trình biên dịch C # phù hợp sẽ thấy rằng một toán tử quá tải có sẵn ở đây và tạo ra đệ quy vô hạn. Hừm. Rắc rối.
Damien_The_Unbeliever

5
Điều đó không trả lời câu hỏi, imho. Nó chỉ giải thích những gì mã được dịch sang chứ không phải tại sao. Theo mục 7.3.4 Độ phân giải quá tải toán tử nhị phân của đặc tả ngôn ngữ C # tôi cũng mong đợi đệ quy vô hạn. Tôi giả sử rằng nguồn tham chiếu ( Referenceource.microsoft.com/#mscorlib/system/iêu ) không thực sự áp dụng ở đây.
Dirk Vollmar

6
@DStanley - Tôi không phủ nhận những gì được sản xuất. Tôi đang nói rằng tôi không thể dung hòa nó với thông số ngôn ngữ. Đó là những gì rắc rối. Tôi đã suy nghĩ về việc ngó qua Roslyn và xem liệu tôi có thể tìm thấy bất kỳ xử lý đặc biệt nào ở đây không nhưng hiện tại tôi không sẵn sàng để làm điều này (máy sai)
Damien_The_Unbeliever

1
@Damien_The_Unbeliever Đó là lý do tại sao tôi nghĩ đó là một ngoại lệ đối với thông số kỹ thuật hoặc cách hiểu khác về các toán tử "tích hợp".
D Stanley

1
Vì @Jon Skeet chưa trả lời, hoặc bình luận về điều này, tôi nghi ngờ đây là một lỗi (tức là vi phạm thông số kỹ thuật).
TheBlastOne

37

Sự nhầm lẫn chính ở đây là bạn cho rằng tất cả các thư viện .NET (trong trường hợp này là Thư viện số mở rộng, không phải là một phần của BCL) được viết bằng C # tiêu chuẩn. Điều này không phải lúc nào cũng đúng, và các ngôn ngữ khác nhau có các quy tắc khác nhau.

Trong C # tiêu chuẩn, đoạn mã bạn nhìn thấy sẽ dẫn đến tràn ngăn xếp, do cách hoạt động của độ phân giải quá tải toán tử. Tuy nhiên, mã không thực sự có trong C # tiêu chuẩn - về cơ bản nó sử dụng các tính năng không có giấy tờ của trình biên dịch C #. Thay vì gọi toán tử, nó phát ra mã này:

ldarg.0
ldarg.1
ceq
ret

Vậy đó :) Không có mã C # tương đương 100% - điều này đơn giản là không thể có trong C # với loại của riêng bạn .

Ngay cả khi đó, toán tử thực tế không được sử dụng khi biên dịch mã C # - trình biên dịch thực hiện một loạt các tối ưu hóa, như trong trường hợp này, trong đó nó thay thế op_Equalitycuộc gọi chỉ bằng cách đơn giản ceq. Một lần nữa, bạn không thể sao chép điều này trong DoubleExcấu trúc của riêng bạn - đó là phép thuật trình biên dịch.

Đây chắc chắn không phải là một tình huống duy nhất trong .NET - có rất nhiều mã không hợp lệ, tiêu chuẩn C #. Những lý do thường là (a) hack trình biên dịch và (b) một ngôn ngữ khác, với các hack thời gian (c) lẻ (tôi đang nhìn vào bạn , Nullable!).

Vì trình biên dịch Roslyn C # là nguồn oepn, tôi thực sự có thể chỉ cho bạn tại nơi quyết định quá tải:

Nơi giải quyết tất cả các toán tử nhị phân

Các "phím tắt" cho các nhà khai thác nội tại

Khi bạn nhìn vào các phím tắt, bạn sẽ thấy sự bình đẳng giữa kết quả gấp đôi và gấp đôi trong toán tử kép nội tại, không bao giờ trong ==toán tử thực được xác định trên loại. Hệ thống loại .NET phải giả vờ Doublelà một loại giống như bất kỳ loại nào khác, nhưng C # không - doublelà nguyên thủy trong C #.


1
Không chắc chắn tôi đồng ý rằng mã trong nguồn tham chiếu chỉ là "thiết kế ngược". Mã này có các chỉ thị của trình biên dịch #ifvà các tạo phẩm khác sẽ không có trong mã được biên dịch. Ngoài ra, nếu nó được thiết kế ngược lại doublethì tại sao nó không được thiết kế ngược lại inthay long? Tôi nghĩ rằng có một lý do cho mã nguồn nhưng tin rằng việc sử dụng ==bên trong toán tử được biên dịch để CEQngăn ngừa đệ quy. Vì toán tử là toán tử "được xác định trước" cho loại đó (và không thể bị ghi đè), các quy tắc quá tải không được áp dụng.
D Stanley

@DStanley Tôi không muốn ám chỉ rằng tất cả các mã được thiết kế ngược. Và một lần nữa, doublekhông phải là một phần của BCL - nó nằm trong một thư viện riêng, điều đó tình cờ được đưa vào đặc tả C #. Đúng, ==được biên dịch thành a ceq, nhưng điều đó vẫn có nghĩa đây là hack trình biên dịch mà bạn không thể sao chép trong mã của riêng mình và một cái gì đó không phải là một phần của đặc tả C # (giống như float64trường trên Doublestruct). Đây không phải là một phần hợp đồng của C #, vì vậy có rất ít điểm trong việc coi nó như C # hợp lệ, ngay cả khi nó được biên dịch bằng trình biên dịch C #.
Luaan 2/2/2016

@DStanely Tôi không thể tìm thấy cách tổ chức khung thực, nhưng trong triển khai tham chiếu .NET 2.0, tất cả các phần khó khăn chỉ là nội tại của trình biên dịch, được triển khai trong C ++. Tất nhiên vẫn còn rất nhiều mã gốc .NET, nhưng những thứ như "so sánh hai nhân đôi" sẽ không thực sự hoạt động tốt trong .NET thuần túy; đó là một trong những lý do số dấu phẩy động không được bao gồm trong BCL. Điều đó nói rằng, mã cũng được triển khai trong C # (không chuẩn), có thể chính xác vì lý do bạn đã đề cập trước đó - để đảm bảo các trình biên dịch .NET khác có thể coi các loại đó là các loại .NET thực.
Luaan 2/2/2016

@DStanley Nhưng không sao, điểm lấy. Tôi đã xóa tham chiếu "thiết kế ngược" và sắp xếp lại câu trả lời để đề cập rõ ràng đến "tiêu chuẩn C #", thay vì chỉ C #. Và đừng đối xử doublegiống như intlong- intlonglà các kiểu nguyên thủy mà tất cả các ngôn ngữ .NET phải hỗ trợ. float, decimaldoublekhông.
Luaan

12

Nguồn gốc của các loại nguyên thủy có thể gây nhầm lẫn. Bạn đã thấy dòng đầu tiên của Doublestruct?

Thông thường bạn không thể định nghĩa một cấu trúc đệ quy như thế này:

public struct Double : IComparable, IFormattable, IConvertible
        , IComparable<Double>, IEquatable<Double>
{
    internal double m_value; // Self-recursion with endless loop?
    // ...
}

Các loại nguyên thủy cũng có hỗ trợ riêng trong CIL. Thông thường chúng không được đối xử như các loại hướng đối tượng. Nhân đôi chỉ là giá trị 64 bit nếu được sử dụng như float64trong CIL. Tuy nhiên, nếu nó được xử lý như một loại .NET thông thường, nó chứa một giá trị thực và nó chứa các phương thức như bất kỳ loại nào khác.

Vì vậy, những gì bạn thấy ở đây là tình huống tương tự cho các nhà khai thác. Thông thường nếu bạn sử dụng loại kép trực tiếp, nó sẽ không bao giờ được gọi. BTW, nguồn của nó trông như thế này trong CIL:

.method public hidebysig specialname static bool op_Equality(float64 left, float64 right) cil managed
{
    .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor()
    .custom instance void __DynamicallyInvokableAttribute::.ctor()
    .maxstack 8
    L_0000: ldarg.0
    L_0001: ldarg.1
    L_0002: ceq
    L_0004: ret
}

Như bạn có thể thấy, không có vòng lặp vô tận ( ceqnhạc cụ được sử dụng thay vì gọi System.Double::op_Equality). Vì vậy, khi một đôi được coi như một đối tượng, phương thức toán tử sẽ được gọi, cuối cùng sẽ xử lý nó như float64kiểu nguyên thủy ở cấp độ CIL.


1
Đối với những người không hiểu phần đầu tiên của bài đăng này (có thể vì họ thường không viết các loại giá trị của riêng họ), hãy thử mã public struct MyNumber { internal MyNumber m_value; }. Nó không thể được biên dịch, tất nhiên. Lỗi là lỗi CS0523: Thành viên cấu trúc 'MyNumber.m_value' thuộc loại 'MyNumber' gây ra một chu kỳ trong bố cục cấu trúc
Jeppe Stig Nielsen

8

Tôi đã xem CIL với JustDecompile. Phần bên trong ==được dịch sang mã op CIL ce . Nói cách khác, đó là sự bình đẳng CLR nguyên thủy.

Tôi tò mò muốn xem liệu trình biên dịch C # sẽ tham chiếu ceqhay ==toán tử khi so sánh hai giá trị kép. Trong ví dụ tầm thường mà tôi đã đưa ra (bên dưới), nó được sử dụngceq .

Chương trình này:

void Main()
{
    double x = 1;
    double y = 2;

    if (x == y)
        Console.WriteLine("Something bad happened!");
    else
        Console.WriteLine("All is right with the world");
}

tạo CIL sau (lưu ý câu lệnh có nhãn IL_0017):

IL_0000:  nop
IL_0001:  ldc.r8      00 00 00 00 00 00 F0 3F
IL_000A:  stloc.0     // x
IL_000B:  ldc.r8      00 00 00 00 00 00 00 40
IL_0014:  stloc.1     // y
IL_0015:  ldloc.0     // x
IL_0016:  ldloc.1     // y
IL_0017:  ceq
IL_0019:  stloc.2
IL_001A:  ldloc.2
IL_001B:  brfalse.s   IL_002A
IL_001D:  ldstr       "Something bad happened!"
IL_0022:  call        System.Console.WriteLine
IL_0027:  nop
IL_0028:  br.s        IL_0035
IL_002A:  ldstr       "All is right with the world"
IL_002F:  call        System.Console.WriteLine
IL_0034:  nop
IL_0035:  ret

-2

Như được chỉ ra trong tài liệu Microsoft cho Không gian tên System.R.78.Versioning: Các loại được tìm thấy trong không gian tên này được sử dụng trong .NET Framework chứ không phải cho các ứng dụng người dùng. Không gian tên System.R.78.Versioning chứa các loại nâng cao hỗ trợ phiên bản trong triển khai bên cạnh .NET Framework.


Phải System.Runtime.Versioninglàm gì với System.Double?
Koopakiller
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.