[C#] Double의 “==”연산자 정의

어떤 이유로 나는 클래스의 .NET Framework 소스를 몰래 들여 Double오고 선언 ==이 다음 과 같은 것을 알았습니다 .

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

모든 연산자에 동일한 논리가 적용됩니다 .


  • 그러한 정의의 요점은 무엇입니까?
  • 어떻게 작동합니까?
  • 왜 무한 재귀를 일으키지 않습니까?


답변

실제로 컴파일러는 ==연산자를 ceqIL 코드로 바꾸며 언급 한 연산자는 호출되지 않습니다.

소스 코드에서 연산자의 이유는 C # 이외의 언어에서 CEQ직접 호출로 (또는 리플렉션을 통해) 호출 하지 않는 언어에서 호출 할 수 있습니다 . 연산자 내의 코드 로 컴파일 CEQ되므로 무한 재귀는 없습니다.

실제로 리플렉션을 통해 연산자를 호출하면 연산자가 CEQ명령이 아닌 호출 되고 프로그램이 예상대로 종료되므로 무한히 재귀하지 않는 것을 알 수 있습니다 .

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

결과 IL (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 

흥미롭게도 – 동일 사업자가 통합 유형 (중 반사 참조 소스 또는 통해) 존재하지 않습니다 만 Single, Double, Decimal, String, 그리고 DateTime, 그들은 다른 언어에서 호출 할 존재 내 이론을 반증한다. 분명히이 연산자없이 다른 언어에서 두 정수를 동일하게 사용할 수 있으므로 “왜 그것들이 존재 double합니까? “라는 질문으로 되돌아갑니다 .


답변

여기서 가장 혼란스러운 점은 모든 .NET 라이브러리 (이 경우 BCL의 일부 가 아닌 확장 숫자 라이브러리 )가 표준 C #으로 작성 되었다고 가정한다는 것 입니다. 항상 그런 것은 아니며 언어마다 규칙이 다릅니다.

표준 C #에서 사용자가보고있는 코드 조각은 연산자 오버로드 확인 작동 방식으로 인해 스택 오버플로가 발생합니다. 그러나 코드는 실제로 표준 C #에 있지 않습니다. 기본적으로 C # 컴파일러의 문서화되지 않은 기능을 사용합니다. 연산자를 호출하는 대신 다음 코드를 생성합니다.

ldarg.0
ldarg.1
ceq
ret

그게 다야 🙂 100 % 동등한 C # 코드는 없습니다. C #에서는 자체 유형 이 불가능합니다 .

그럼에도 불구하고 실제 연산자는 C # 코드를 컴파일 할 때 사용되지 않습니다. 컴파일러는이 경우와 같이 op_Equality호출을 단순한로 대체하는 많은 최적화를 수행합니다 ceq. 다시 말하지만, 자신의 DoubleEx구조체 에서 이것을 복제 할 수 없습니다 -컴파일러 마술입니다.

이것은 분명히 .NET의 독특한 상황은 아닙니다. 표준 C #에는 유효하지 않은 코드가 많이 있습니다. 그 이유는 일반적으로 (a) 컴파일러 해킹과 (b) 이상한 (c) 런타임 해킹과 함께 다른 언어입니다 (나는 당신을보고 있습니다 Nullable!).

Roslyn C # 컴파일러는 oepn 소스이므로 실제로 과부하 해결이 결정되는 위치를 알려줄 수 있습니다.

모든 이진 연산자가 해결되는 장소

내장 연산자의 “바로 가기”

바로 가기를 보면 double과 double 사이의 동등성 으로 인해 유형에 정의 된 실제 연산자가 아닌 내장형 이중 연산자가 나타납니다 ==. .NET 형식 시스템은 Double다른 형식과 같은 척해야 하지만 C #은 그렇지 않습니다 .C # double의 기본 요소입니다.


답변

기본 유형의 소스는 혼란 스러울 수 있습니다. Double구조체의 첫 번째 줄을 보셨습니까 ?

일반적으로 다음과 같이 재귀 구조체를 정의 할 수 없습니다.

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

기본 유형은 CIL에서 기본적으로 지원됩니다. 일반적으로 객체 지향 유형처럼 취급되지 않습니다. float64CIL에서 와 같이 사용되는 경우 double은 64 비트 값 입니다. 그러나 일반적인 .NET 유형으로 처리되는 경우 실제 값을 포함하고 다른 유형과 같은 메소드를 포함합니다.

여기 보시는 것은 운영자에게도 같은 상황입니다. 일반적으로 이중 유형 유형을 직접 사용하면 호출되지 않습니다. BTW, 소스는 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
}

보시다시피, 무한 루프가 없습니다 ( ceq을 호출하는 대신 계측기가 사용됨 System.Double::op_Equality). 따라서 double이 객체처럼 취급되면 연산자 메소드가 호출되어 결국 float64CIL 수준 에서 기본 유형 으로 처리 됩니다.


답변

JustDecompile 을 사용하여 CIL 을 살펴 보았습니다. 내부 ==는 CIL ceq op 코드로 변환됩니다 . 다시 말해, 기본 CLR 평등입니다.

두 개의 이중 값을 비교할 때 C # 컴파일러가 참조 연산자 ceq인지 ==연산자 인지 궁금 합니다. 사소한 예에서 나는 (아래)를 생각해 냈습니다 ceq.

이 프로그램:

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

다음 CIL을 생성합니다 (label이있는 명령문에주의하십시오 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


답변

System.Runtime.Versioning 네임 스페이스에 대한 Microsoft 설명서에 표시된대로이 네임 스페이스에있는 형식은 사용자 응용 프로그램이 아닌 .NET Framework 내에서 사용하도록되어 있습니다 .System.Runtime.Versioning 네임 스페이스에는 버전 관리를 지원하는 고급 형식이 포함되어 있습니다. .NET Framework의 단계별 구현.


답변