【问题标题】:Are string.Equals() and == operator really same? [duplicate]string.Equals() 和 == 运算符真的一样吗? [复制]
【发布时间】:2010-09-09 17:00:04
【问题描述】:

它们真的一样吗?今天,我遇到了这个问题。这是来自即时窗口的转储:

?s 
"Category" 
?tvi.Header 
"Category" 
?s == tvi.Header 
false 
?s.Equals(tvi.Header) 
true 
?s == tvi.Header.ToString() 
true 

所以,stvi.Header 都包含“类别”,但 == 返回 false,Equals() 返回 true。

s被定义为字符串,tvi.Header实际上是一个WPFTreeViewItem.Header。那么,为什么他们会返回不同的结果呢?我一直认为它们在 C# 中是可以互换的。

谁能解释这是为什么?

【问题讨论】:

  • 我认为 string.Equals 匹配整个对象
  • 是的,我从我的代码中得到了相同的结果。实际上,它引起了我的注意,因为 == 在代码中返回 false。我总是使用 == 进行字符串比较。今天早上,当我看到双方都包含相同的字符串“Category”时,我简直不敢相信 == 返回 false(我什至要求我的同事仔细检查我是否发现有问题)。但是当我将其更改为使用 Equals 时效果很好(与立即窗口中显示的结果相同)。
  • 对于大多数字符串比较,您应该考虑调用包含StringComparison 参数的string.Equals 重载。将 InvarientCulture 版本用于编码字符串(例如 XML 属性)或 CurrentCulture 版本用于用户输入的字符串。这将处理 == 忽略的许多细节,例如 Unicode 字符规范化形式等,并且明确区分大小写。
  • @Robaticus,我不明白你在说什么。
  • == 仅在对象的静态类型为字符串时才有效。由于运算符重载只考虑静态类型。

标签: c# string


【解决方案1】:

两个区别:

  • Equals 是多态的(即它可以被覆盖,并且使用的实现将取决于目标对象的执行时类型),而使用的== 的实现是基于编译确定的-time 对象的类型:

      // Avoid getting confused by interning
      object x = new StringBuilder("hello").ToString();
      object y = new StringBuilder("hello").ToString();
      if (x.Equals(y)) // Yes
    
      // The compiler doesn't know to call ==(string, string) so it generates
      // a reference comparision instead
      if (x == y) // No
    
      string xs = (string) x;
      string ys = (string) y;
    
      // Now *this* will call ==(string, string), comparing values appropriately
      if (xs == ys) // Yes
    
  • Equals 在 null 上调用它会抛出异常,== 不会

      string x = null;
      string y = null;
    
      if (x.Equals(y)) // NullReferenceException
    
      if (x == y) // Yes
    

请注意,使用object.Equals 可以避免后者成为问题:

if (object.Equals(x, y)) // Fine even if x or y is null

【讨论】:

  • 不 == 还包括一个 Object.ReferenceEquals 检查快速true 如果它们是同一个对象?
  • x == y 等于 false,因为您正在使用对象类的相等运算符检查引用相等性。 (string)x == (string)y 实际上返回 true,至少在 .Net 4.0 中。
  • 很好的答案。使用String.Equals(x, y) 而不是object.Equals(x, y) 来避免null 问题不是更有意义吗?
  • @Jon Skeet 我指的是类型检查比object.Equals(x, y) 更有优势。问题是关于字符串比较的,所以看起来增加的类型检查将是使用String.Equals() 而不是object.Equals() 的好处。有没有理由避免这种类型检查会更好?另外,感谢您回复关于一年多的问题的评论!
  • @Chaulky:没有什么特别的原因——除了我知道object.Equals总是可以避免无效问题,而对于特定类型,我需要检查文档: )
【解决方案2】:

问题中出现明显的矛盾是因为在一种情况下,Equals 函数是在 string 对象上调用的,而在另一种情况下,== 运算符是在 System.Object 类型上调用的。 stringobject 实现相等的方式彼此不同(分别是值与引用)。

除此之外,任何类型都可以以不同的方式定义==Equals,因此它们通常不可互换。

这是一个使用 double 的示例(来自 Joseph Albahari 对 C# 语言规范第 7.9.2 节的注释):

double x = double.NaN;
Console.WriteLine (x == x);         // False
Console.WriteLine (x != x);         // True
Console.WriteLine (x.Equals(x));    // True

他接着说double.Equals(double) 方法旨在正确处理列表和字典。另一方面,== 运算符旨在遵循浮点类型的 IEEE 754 标准。

在确定字符串相等性的特定情况下,行业偏好是大多数时间既不使用==,也不使用string.Equals(string)。这些方法确定两个字符串是否逐个字符相同,这很少是正确的行为。最好使用string.Equals(string, StringComparison),它允许您指定特定类型的比较。通过使用正确的比较,您可以避免很多潜在的(非常难以诊断的)错误。

这是一个例子:

string one = "Caf\u00e9";        // U+00E9 LATIN SMALL LETTER E WITH ACUTE
string two = "Cafe\u0301";       // U+0301 COMBINING ACUTE ACCENT
Console.WriteLine(one == two);                                          // False
Console.WriteLine(one.Equals(two));                                     // False
Console.WriteLine(one.Equals(two, StringComparison.InvariantCulture));  // True

此示例中的两个字符串看起来相同(“Café”),因此如果使用简单(序数)相等,这可能很难调试。

【讨论】:

    【解决方案3】:

    C# 有两个“等于”概念:EqualsReferenceEquals。对于您将遇到的大多数类,== 运算符使用一个或另一个(或两者),并且通常仅在处理引用类型时测试 ReferenceEquals(但 string 类是 C# 已经知道如何测试值相等)。

    • Equals 比较值。 (即使两个独立的int 变量不存在于内存中的同一位置,它们仍然可以包含相同的值。)
    • ReferenceEquals 比较引用并返回操作数是否指向内存中的同一个对象。

    示例代码:

    var s1 = new StringBuilder("str");
    var s2 = new StringBuilder("str");
    StringBuilder sNull = null;
    
    s1.Equals(s2); // True
    object.ReferenceEquals(s1, s2); // False
    s1 == s2 // True - it calls Equals within operator overload
    s1 == sNull // False
    object.ReferenceEquals(s1, sNull); // False
    s1.Equals(sNull); // Nono!  Explode (Exception)
    

    【讨论】:

    • 这不太正确...尝试将两个实际的字符串对象与== 进行比较,它们的引用不同但值相同,您将得到true。 Jon Skeet 解释了这一点……
    • @Noldorin:这就是为什么我说:“== 运算符有时必须在它们之间进行选择,而通常在处理可空类型时选择ReferenceEquals。” string 是可空类型的示例,C# 不仅仅使用 ReferenceEquals。 OP 询问了 string 对象,但确实需要了解一般 C# 对象。
    • 它不必在它们之间进行选择,它可以做一些不同的事情。通常不是一个好主意,但它可以。
    • @palswim 您在评论中写道“字符串是可空类型的示例”,但可空类型是值类型的子类别。字符串是引用类型。请参阅规范的第 1.3 节
    【解决方案4】:

    TreeViewItemHeader 属性被静态类型化为object 类型。

    因此== 产生false。您可以使用以下简单的 sn-p 重现这一点:

    object s1 = "Hallo";
    
    // don't use a string literal to avoid interning
    string s2 = new string(new char[] { 'H', 'a', 'l', 'l', 'o' });
    
    bool equals = s1 == s2;         // equals is false
    equals = string.Equals(s1, s2); // equals is true
    

    【讨论】:

    • 这是 WPF 的东西吗?我刚开始使用 WPF。我以前从未在 WinForm 应用程序中遇到过此类问题。所以,我们应该总是使用 Equals 而不是 ==?
    • 值得注意的是,s1 == s2 变为 s1.Equals(s2) IFF s1 和 s2 都被声明为字符串。字符串相等在 C# 中具有特殊含义。
    • @miliu:从我的示例中可以看出,这与 WPF 无关,但在 .NET 中通常是这种情况。
    • 我相信它实际上是拼写为“H-e-l-l-o”
    【解决方案5】:

    除了Jon Skeet's answer,我想解释一下为什么大多数时候在使用== 时,你实际上会在具有相同值的不同字符串实例上得到true 的答案:

    string a = "Hell";
    string b = "Hello";
    a = a + "o";
    Console.WriteLine(a == b);
    

    如您所见,ab 必须是不同的字符串实例,但由于字符串是不可变的,运行时使用所谓的 string interning 来让 ab 引用相同的字符串记忆。对象的== 运算符检查引用,由于ab 都引用同一个实例,结果是true。当您更改其中任何一个时,都会创建一个新的字符串实例,这就是为什么可以进行字符串实习的原因。

    顺便说一句,Jon Skeet 的回答并不完整。确实,x == yfalse,但这只是因为他在比较对象,而对象是通过引用进行比较的。如果你写(string)x == (string)y,它将再次返回true。所以字符串的 == 运算符重载,它在下面调用 String.Equals

    【讨论】:

    • 我认为这从答案中已经很明显了,但我已经对其进行了编辑以使其更加完整。但是,您关于实习的回答是正确的。 ab 指的是上述不同的实例;字符串实习仅适用于编译时常量。您的代码打印 True 因为它调用了 == 重载,它正在比较字符序列。 a 最终引用的字符串没有被保留。
    • @Jon Skeet:为什么a 最终引用的字符串不是实习生?是不是因为无法确定比较时a的编译时值?初始的"Hell" 值会被保留吗?
    • @Ax: 是的——它不是编译时常量。 “地狱”确实会被拘留。 (由于b,实习生池中当然会存在“Hello”;但a将是调用String.Concat("Hell", "o");的结果
    • @Ax.:系统仅在明确请求时才实习非常量字符串,部分原因是一旦一个字符串被实习,实习副本永远不会被垃圾收集。如果一个人碰巧经常使用包含 32,000 个字符的特定序列的字符串,那么对于每个本来会创建的副本,对字符串进行实习可以节省 64K。但是,如果实习生一个 32,000 个字符的字符串并且不再使用该字符序列,则将永久浪费 64K 的内存。程序不能这样做太多次。
    • @Ax.: 虽然系统可能会在创建字符串时检查它是否在实习生池中,而如果它不在则尝试添加它,巨大的在大多数程序中创建的大多数字符串不会在实习生池中。根据实习生池检查每个生成的字符串,以防万一它的副本可能存在,这通常会浪费更多的时间而不是节省的时间。
    【解决方案6】:

    这里有很多描述性的答案,所以我不会重复已经说过的内容。我想添加的是以下代码,展示了我能想到的所有排列。由于组合的数量,代码很长。随意将其放入 MSTest 并自己查看输出(输出包含在底部)。

    这个证据支持 Jon Skeet 的回答。

    代码:

    [TestMethod]
    public void StringEqualsMethodVsOperator()
    {
        string s1 = new StringBuilder("string").ToString();
        string s2 = new StringBuilder("string").ToString();
    
        Debug.WriteLine("string a = \"string\";");
        Debug.WriteLine("string b = \"string\";");
    
        TryAllStringComparisons(s1, s2);
    
        s1 = null;
        s2 = null;
    
        Debug.WriteLine(string.Join(string.Empty, Enumerable.Repeat("-", 20)));
        Debug.WriteLine(string.Empty);
        Debug.WriteLine("string a = null;");
        Debug.WriteLine("string b = null;");
    
        TryAllStringComparisons(s1, s2);
    }
    private void TryAllStringComparisons(string s1, string s2)
    {
        Debug.WriteLine(string.Empty);
        Debug.WriteLine("-- string.Equals --");
        Debug.WriteLine(string.Empty);
        Try((a, b) => string.Equals(a, b), s1, s2);
        Try((a, b) => string.Equals((object)a, b), s1, s2);
        Try((a, b) => string.Equals(a, (object)b), s1, s2);
        Try((a, b) => string.Equals((object)a, (object)b), s1, s2);
    
        Debug.WriteLine(string.Empty);
        Debug.WriteLine("-- object.Equals --");
        Debug.WriteLine(string.Empty);
        Try((a, b) => object.Equals(a, b), s1, s2);
        Try((a, b) => object.Equals((object)a, b), s1, s2);
        Try((a, b) => object.Equals(a, (object)b), s1, s2);
        Try((a, b) => object.Equals((object)a, (object)b), s1, s2);
    
        Debug.WriteLine(string.Empty);
        Debug.WriteLine("-- a.Equals(b) --");
        Debug.WriteLine(string.Empty);
        Try((a, b) => a.Equals(b), s1, s2);
        Try((a, b) => a.Equals((object)b), s1, s2);
        Try((a, b) => ((object)a).Equals(b), s1, s2);
        Try((a, b) => ((object)a).Equals((object)b), s1, s2);
    
        Debug.WriteLine(string.Empty);
        Debug.WriteLine("-- a == b --");
        Debug.WriteLine(string.Empty);
        Try((a, b) => a == b, s1, s2);
    #pragma warning disable 252
        Try((a, b) => (object)a == b, s1, s2);
    #pragma warning restore 252
    #pragma warning disable 253
        Try((a, b) => a == (object)b, s1, s2);
    #pragma warning restore 253
        Try((a, b) => (object)a == (object)b, s1, s2);
    }
    public void Try<T1, T2, T3>(Expression<Func<T1, T2, T3>> tryFunc, T1 in1, T2 in2)
    {
        T3 out1;
    
        Try(tryFunc, e => { }, in1, in2, out out1);
    }
    public bool Try<T1, T2, T3>(Expression<Func<T1, T2, T3>> tryFunc, Action<Exception> catchFunc, T1 in1, T2 in2, out T3 out1)
    {
        bool success = true;
        out1 = default(T3);
    
        try
        {
            out1 = tryFunc.Compile()(in1, in2);
            Debug.WriteLine("{0}: {1}", tryFunc.Body.ToString(), out1);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("{0}: {1} - {2}", tryFunc.Body.ToString(), ex.GetType().ToString(), ex.Message);
            success = false;
            catchFunc(ex);
        }
    
        return success;
    }
    

    输出:

    string a = "string";
    string b = "string";
    
    -- string.Equals --
    
    Equals(a, b): True
    Equals(Convert(a), b): True
    Equals(a, Convert(b)): True
    Equals(Convert(a), Convert(b)): True
    
    -- object.Equals --
    
    Equals(a, b): True
    Equals(Convert(a), b): True
    Equals(a, Convert(b)): True
    Equals(Convert(a), Convert(b)): True
    
    -- a.Equals(b) --
    
    a.Equals(b): True
    a.Equals(Convert(b)): True
    Convert(a).Equals(b): True
    Convert(a).Equals(Convert(b)): True
    
    -- a == b --
    
    (a == b): True
    (Convert(a) == b): False
    (a == Convert(b)): False
    (Convert(a) == Convert(b)): False
    --------------------
    
    string a = null;
    string b = null;
    
    -- string.Equals --
    
    Equals(a, b): True
    Equals(Convert(a), b): True
    Equals(a, Convert(b)): True
    Equals(Convert(a), Convert(b)): True
    
    -- object.Equals --
    
    Equals(a, b): True
    Equals(Convert(a), b): True
    Equals(a, Convert(b)): True
    Equals(Convert(a), Convert(b)): True
    
    -- a.Equals(b) --
    
    a.Equals(b): System.NullReferenceException - Object reference not set to an instance of an object.
    a.Equals(Convert(b)): System.NullReferenceException - Object reference not set to an instance of an object.
    Convert(a).Equals(b): System.NullReferenceException - Object reference not set to an instance of an object.
    Convert(a).Equals(Convert(b)): System.NullReferenceException - Object reference not set to an instance of an object.
    
    -- a == b --
    
    (a == b): True
    (Convert(a) == b): True
    (a == Convert(b)): True
    (Convert(a) == Convert(b)): True
    

    【讨论】:

      【解决方案7】:

      很明显tvi.header 不是String== 是一个被String 类重载的运算符,这意味着它只有在编译器知道运算符的两边都是String 时才能工作。

      【讨论】:

        【解决方案8】:

        对象由唯一的 OBJECT_ID 定义。如果 A 和 B 是对象并且 A == B 为真,那么它们就是同一个对象,它们具有相同的数据和方法,但是,这也是真的:

        A.OBJECT_ID == B.OBJECT_ID

        如果 A.Equals(B) 为真,表示两个对象处于相同的状态,但这并不意味着 A 和 B 完全一样。

        字符串是对象。

        请注意,== 和 Equals 运算符是自反的、同分的、传递的,因此它们是等价关系(使用关系代数术语)

        这意味着什么: 如果A、B、C是对象,那么:

        (1) A == A 始终为真; A.Equals(A) 始终为真(自反性)

        (2) 如果 A == B 那么 B == A;如果 A.Equals(B) 则 B.Equals(A) (simetry)

        (3) 如果 A == B 且 B == C,则 A == C; if A.Equals(B) and B.Equals(C) then A.Equals(C) (transzitivity)

        另外,你可以注意到这也是正确的:

        (A == B) => (A.Equals(B)),但反之不成立。

        A B =>
        0 0 1
        0 1 1
        1 0 0
        1 1 1
        

        现实生活的例子: 相同类型的两个汉堡包具有相同的属性:它们是汉堡包类的对象,它们的属性完全相同,但它们是不同的实体。如果你买了这两个汉堡,吃了一个,另一个就吃不下了。因此,Equals 和 == 之间的区别: 你有 hamburger1 和 hamburger2。它们完全处于相同的状态(相同的重量、相同的温度、相同的味道),所以 hamburger1.Equals(hamburger2) 为真。但是hamburger1 == hamburger2是假的,因为如果hamburger1的状态改变了,hamburger2的状态不一定改变,反之亦然。

        如果你和一个朋友同时得到一个汉堡包,这是你和他的,那么你必须决定把汉堡包分成两部分,因为 you.getHamburger() == friend.getHamburger() 是 true 并且如果发生这种情况:friend.eatHamburger(),那么你的汉堡也会被吃掉。

        我可以写关于 Equals 和 == 的其他细微差别,但我饿了,所以我必须走了。

        最好的问候, 拉霍斯阿帕德。

        【讨论】:

        • 我想知道投反对票的原因(我仍然不明白为什么我的评论有误)。也许投反对票的人可以告诉我他认为我在哪里不正确。
        • 不是我的反对意见,但您的帖子中有几个错误可能导致它。 A == A 不保证 A.Equals(A) 对于对象它们只是两个方法调用,并且不能保证这些调用的结果进一步相同 (2) if A == B then B == A;如果 A.Equals(B) 则 B.Equals(A) (simetry) 也不正确,因为 A 和 B 可能有两种不同的运行时类型,每个都有自己的 Equals 重载产生不同的结果,最后 A == B 和 B == C 不会导致 A == C. 取这段代码字符串 A = "foo";字符串 B = "foo";对象 C = B; A == C 是假的
        • 如果你取任何对象,A == A 为真。如果你观察一个对象是否等于它自己,它总是正确的。您正在评论两个不同对象的 == 和 Equals。 (new Foo()) == (new Foo()) 永远不会为真,但尽管您使用相同的语法生成它们,但它们是两个不同的对象。 A、B 和 C 表示对象,而不是函数调用。如果您有一个生成对象的函数调用,则两个不同调用的结果将是两个不同的对象。 A == A 总是正确的,一个对象与它自己是一样的。 A.Equals(A) 始终为真,A 等于自身
        • A == A 意味着 A.OBJECT_ID == A.OBJECT_ID 和 A.Equals(A) 总是正确的(我们在这里谈论的是对象,而不是函数调用)
        • 在 C# 中,== 标记用于表示两种不同的运算符:可重载(但非虚拟)相等运算符和固定引用相等运算符。如果== 运算符与已明确定义重载的类型组合一起使用(例如比较两个字符串),它将使用该重载。因此,String 类型的两个参数之间的比较将检查相同内容的字符串,而 StringObject 之间的比较将检查其参数的对象 ID。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-03-15
        • 1970-01-01
        • 2015-09-16
        • 2010-12-03
        • 2015-09-26
        • 1970-01-01
        • 2021-08-30
        相关资源
        最近更新 更多