【问题标题】:Why is Equals between long and decimal not commutative?为什么 long 和 decimal 之间的 Equals 不可交换?
【发布时间】:2015-01-23 20:54:05
【问题描述】:

我有这段代码在 linqpad 中运行:

    long x = long.MaxValue;
    decimal y = x;

    x.Dump();
    y.Dump();

    (x == y).Dump();
    (y == x).Dump();

    Object.Equals(x, y).Dump();
    Object.Equals(y, x).Dump();
    x.Equals(y).Dump();
    y.Equals(x).Dump();

它产生这个输出:

    9223372036854775807
    9223372036854775807
    True
    True
    False
    False
    False
    True

注意最后两行:x.Equals(y) 为假,但 y.Equals(x) 为真。所以小数认为自己等于具有相同值的长,但长不认为自己等于具有相同值的小数。

这种行为的解释是什么?

更新:

我接受了李的回答。

我对此很好奇,就写了这个小程序:

using System;
namespace TestConversion
{
  class Program
  {
    static void Main(string[] args)
    {
      long x = long.MaxValue;
      decimal y = x;

      Console.WriteLine(x);
      Console.WriteLine(y);

      Console.WriteLine(x == y);
      Console.WriteLine(y == x);

      Console.WriteLine(Object.Equals(x, y));
      Console.WriteLine(Object.Equals(y, x));
      Console.WriteLine(x.Equals(y));
      Console.WriteLine(y.Equals(x));
      Console.ReadKey();
    }
  }
}

然后我在 IL 中反汇编:

.method private hidebysig static void Main(string[] args) cil managed
{
  .entrypoint
  .maxstack 2
  .locals init (
    [0] int64 x,
    [1] valuetype [mscorlib]System.Decimal y)
  L_0000: nop 
  L_0001: ldc.i8 9223372036854775807
  L_000a: stloc.0 
  L_000b: ldloc.0 
  L_000c: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(int64)
  L_0011: stloc.1 
  L_0012: ldloc.0 
  L_0013: call void [mscorlib]System.Console::WriteLine(int64)
  L_0018: nop 
  L_0019: ldloc.1 
  L_001a: call void [mscorlib]System.Console::WriteLine(valuetype [mscorlib]System.Decimal)
  L_001f: nop 
  L_0020: ldloc.0 
  L_0021: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(int64)
  L_0026: ldloc.1 
  L_0027: call bool [mscorlib]System.Decimal::op_Equality(valuetype [mscorlib]System.Decimal, valuetype [mscorlib]System.Decimal)
  L_002c: call void [mscorlib]System.Console::WriteLine(bool)
  L_0031: nop 
  L_0032: ldloc.1 
  L_0033: ldloc.0 
  L_0034: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(int64)
  L_0039: call bool [mscorlib]System.Decimal::op_Equality(valuetype [mscorlib]System.Decimal, valuetype [mscorlib]System.Decimal)
  L_003e: call void [mscorlib]System.Console::WriteLine(bool)
  L_0043: nop 
  L_0044: ldloc.0 
  L_0045: box int64
  L_004a: ldloc.1 
  L_004b: box [mscorlib]System.Decimal
  L_0050: call bool [mscorlib]System.Object::Equals(object, object)
  L_0055: call void [mscorlib]System.Console::WriteLine(bool)
  L_005a: nop 
  L_005b: ldloc.1 
  L_005c: box [mscorlib]System.Decimal
  L_0061: ldloc.0 
  L_0062: box int64
  L_0067: call bool [mscorlib]System.Object::Equals(object, object)
  L_006c: call void [mscorlib]System.Console::WriteLine(bool)
  L_0071: nop 
  L_0072: ldloca.s x
  L_0074: ldloc.1 
  L_0075: box [mscorlib]System.Decimal
  L_007a: call instance bool [mscorlib]System.Int64::Equals(object)
  L_007f: call void [mscorlib]System.Console::WriteLine(bool)
  L_0084: nop 
  L_0085: ldloca.s y
  L_0087: ldloc.0 
  L_0088: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(int64)
  L_008d: call instance bool [mscorlib]System.Decimal::Equals(valuetype [mscorlib]System.Decimal)
  L_0092: call void [mscorlib]System.Console::WriteLine(bool)
  L_0097: nop 
  L_0098: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  L_009d: pop 
  L_009e: ret 
}

您确实可以看到 long 值转换为十进制。

谢谢你们!

【问题讨论】:

  • fyi,将小数点转换回长的作品:x.Equals((long)y).Dump();
  • 虽然这并不能完全解释这种行为,但您可能想看看这个:stackoverflow.com/questions/485175/…
  • @leppie:在实际程序中,我们不会比较它们。我们通过一些调试发现了这一点,我发现它很奇怪 - 因此我的帖子。
  • @costa 考虑更新标题以明确讨论长/小数比较...
  • @costa 你还在问 Equals 是否应该是交流的吗?如果是这样,是的,Equals 应该是交流性和传递性的。但这只是一个指导方针。我以前有理由自己打破这条准则。但是在这种情况下,关于长小数比较,听起来确实像 MS 搞砸了。 Equals 应该检查值是否相等。另请参阅msdn.microsoft.com/en-US/library/ms173147(v=vs.80).aspx,其中明确指出 Equals 的实现应保证 x.Equals(y) 和 y.Equals(x) 返回相同的值。

标签: c# .net


【解决方案1】:

发生这种情况是因为在

y.Equals(x);

decimal.Equals(decimal) 重载被调用,因为在 longdecimal 之间有一个 implicit conversion。结果,比较返回 true。

但是,由于没有从 decimallong 的隐式转换

x.Equals(y)

调用long.Equals(object) 导致y 被装箱,比较返回false,因为它不能被取消装箱。

【讨论】:

  • 这个答案加上其他答案的链接 - stackoverflow.com/a/28118709/477420 给出了完整的解释。
  • 这个答案很奇怪,但在某种程度上仍然感觉正确......你是说虽然小数知道 long,但 long 不知道小数(就转换而言)?
  • 您正在查看错误的转换。 1.1 不能在 long 中表示。所以从decimallong 的转换必须是显式的,因为它会导致精度损失。从longdecimal 的转换是隐式的。
  • 同理。因为没有从ulonglong 的隐式转换。那为什么呢?因为有些值可以在ulong 中表示,但不能在long 中表示。
  • @leppie - 未比较位模式。选择Equals(object) 重载是因为没有从ulonglong 的隐式转换。参数是装箱的,long.Equals(object) 方法必须做的第一件事是查看参数是否是装箱的 long 来进行比较。这失败了,因为参数是一个装箱的ulong
【解决方案2】:

隐式与显式转换。

来自MSDN

隐式转换:不需要特殊语法,因为 转换是类型安全的,不会丢失任何数据。例子包括 从较小的整数类型到较大的整数类型的转换,以及转换 从派生类到基类。

显式转换(强制转换):显式转换需要强制转换 操作员。当信息可能在 转换,或者当其他转换可能不成功时 原因。典型示例包括将数值转换为 具有较低的精度或较小的范围,以及基类的转换 派生类的实例。

long 很容易转换为小数,但反过来就不行,所以求值失败。

【讨论】:

    【解决方案3】:

    您正在比较对象引用和值。引用当然不一样 - 除了您在第 2 行明确设置的引用。但是值是。

    C# 会自动为您处理指针管理(例如“引用内存”)。您正在访问最初从语法中不明显的层。这就是 C# 的本质。

    【讨论】:

    • 它们都是值类型
    • 如果指针不相同,Object.Equals 将失败,因为对象确实不相等。
    • 这里没有调用Object.equals
    猜你喜欢
    • 1970-01-01
    • 2021-01-13
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    • 1970-01-01
    • 1970-01-01
    • 2011-12-10
    • 2014-10-09
    相关资源
    最近更新 更多