Comparing VALUE and REFERENCE of types

2019-06-04 00:43发布

问题:

I know there are a lot of ways to compare VALUE and REFERENCES in C#, but I'm still a bit confused about what type performs what when you try to compare either VALUE or REFERENCE.

String examples:

string str = "hello";
string str2 = "hello";

if (str == str2)
{
   Console.WriteLine("Something");
} // Is this a comparison of value?

if (str.Equals(str2))
{
   Console.WriteLine("Something");
} // Is this a comparison of value?

string.ReferenceEquals(str, str2); // Comparison of reference (True)

Console.WriteLine((object)str1 == (object)str2); // Comparison of reference (True)

回答1:

Equals and == will compare by reference by default if they're not overriden / overloaded in a subclass. ReferenceEquals will always compare by reference.

Strings are a confusing data type to use for experimenting with this, because they overload == to implement value equality; also, since they're immutable, C# will generally reuse the same instance for the same literal string. In your code, str and str2 will be the same object.



回答2:

@Inerdia is right with what he says but I'd like to point out the reason why the line string.ReferenceEquals(str, str2) returns true in your code example. Because you are defining both of the strings at compile time, the compiler can optimise the code so they can both point to the same instance of the string. Since strings are immutable the compiler knows it can do this even though String is a reference type. But If you change your code to dynamically generate one of the strings (as shown below) the compiler can't perform this optimisation. So in your code example if you change your code to:

string str = "hello";
string str2 = new StringBuilder().Append("he").Append("llo").ToString(); 

Then the string.ReferenceEquals(str, str2) line will now return false as this time the compiler cant know to re-use the same instance (reference of the string).



回答3:

  1. string.ReferenceEquals(str, str2);
    It obviously compares references.
  2. str.Equals(str2)
    Tries to compare references at first. Then it tries to compare by value.
  3. str == str2
    Does the same as Equals.

A good way to compare strings is to use string.Compare. If you want to ignore case, there is a parameter in place for that too.



回答4:

Equality and Comparision of ReferenceTypes and strings:

Reference types work like this:

System.Object a = new System.Object();
System.Object b = new System.Object();
a == b;      //returns true
a.Equals(b); //returns false

b = a;
a == b;      //returns true
a.Equals(b); //returns true

Since strings are Reference types they should do the same, shouldn't they? But they don't!

C# Documentation defines string equality like this:

Although string is a reference type, the equality operators (== and !=) are defined to compare the values of string objects, not references (7.9.7 String equality operators). This makes testing for string equality more intuitive.

https://msdn.microsoft.com/en-us/library/362314fe%28v=vs.71%29.aspx https://msdn.microsoft.com/en-us/library/aa664728%28v=vs.71%29.aspx

This has implications for you test code.

if (str == str2)
{
   Console.WriteLine("Something");
} // This is comparision of value even though string is a referenceType

if (str.Equals(str2))
{
   Console.WriteLine("Something");
} // This is comparison by value too, because Equals is overrided in String class.

Keep in mind you as a programmer (Or your tricky coworker) can override .Equals(), changing it's behaviour, what you see above is what should happen. It's not necessarily in line with your codebase-reality, when in doubt check out the definition by marking .Equals() and hitting F12.

Addendum for x.Equals

The behavior of object.Equals() should these rules:

  • List item
  • x.Equals(x) returns true.
  • x.Equals(y) returns the same value as y.Equals(x).
  • if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true.
  • Successive invocations of x.Equals(y) return the same value as long as the objects referenced by x and y are not modified.
  • x.Equals(null) returns false. https://msdn.microsoft.com/ru-ru/library/ms173147%28v=vs.80%29.aspx

Whenever you are in doubt you can call x.ReferenceEquals, it's defined as following:

Unlike the Object.Equals(Object) method and the equality operator, the Object.ReferenceEquals(Object) method cannot be overridden. Because of this, if you want to test two object references for equality and you are unsure about the implementation of the Equals method, you can call the method.

https://msdn.microsoft.com/de-de/library/system.object.referenceequals%28v=vs.110%29.aspx

Thus:

System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

In your example the compiler merges your strings in optimization thus:

string str = "hello";
string str2 = "hello";
string.ReferenceEquals(str, str2); // Comparison of reference (True)

This behaviour is only duo to compiler optimization in your example, if we randomize the code it will return false:

string str = "hello";
string str2 = "hello";
if(throwCoin)
{ 
   str2 = "bye";
}   
string.ReferenceEquals(str, str2); // Comparison of reference (False)


回答5:

Excerpt from .net sources:

public bool Equals(string value)
{
  if (this == null)
    throw new NullReferenceException();
  else if (value == null)
    return false;
  else if (object.ReferenceEquals((object) this, (object) value))
    return true;
  else
    return string.EqualsHelper(this, value);
}

So in general it is comparision of references first and if they don't match, it compares values.