Comparing two objects with == operator

C#.Net.Net 6.0

C# Problem Overview


I have this code that checks the references of two variables, I came across this case which is a bit confusing :

string first = "10";
object second = 10.ToString();
dynamic third = second;

Console.WriteLine($"{first == second}   {first == third}");

The result is : False True

My first question is why are the first and third references equal? If the third variable is equal to the second it should be False because their object references aren't equal.

And I got confused when I changed the values to "1" like below:

string first = "1";
object second = 1.ToString();
dynamic third = second;

Console.WriteLine($"{first == second}   {first == third}");

Then the result becomes: True True

Why does this happen?

C# Solutions


Solution 1 - C#

> I am not sure why it changes when you change it from 10 to 1

I believe this is an implementation detail and you should not rely on it (will try to find something in the specs) but some positive single digit numbers are cached in int.ToString implementation for .NET Core. Here is excerpt from UInt32ToDecStr which is called internally by int.ToString:

// For single-digit values that are very common, especially 0 and 1, just return cached strings.
if (bufferLength == 1)
{
    return s_singleDigitStringCache[value];
}

As for equality - please check:

  1. C# difference between == and Equals().
  2. String interning in .Net Framework. (compiler will intern string literals, so all of them will point to the same address in memory)
  3. Using type dynamic

UPD:

Was not able to find anything in specs, but next code behaves differently in .NET Framework and .NET 6 (former one prints 11 times False and the latter prints 10 times True and one False):

var dict = new Dictionary<int, string>()
{
    {0, "0"},
    {1, "1"},
    {2, "2"},
    {3, "3"},
    {4, "4"},
    {5, "5"},
    {6, "6"},
    {7, "7"},
    {8, "8"},
    {9, "9"},
    {10, "10"},
};

foreach(var kvp in dict)
{
    Console.WriteLine(object.ReferenceEquals(kvp.Key.ToString(), kvp.Value));
}

UPD2:

The caching was introduced for performance reasons by this PR and is mentioned in Performance Improvements in .NET Core 3.0 blogpost:

> In some sizeable web applications, we found that a large number of strings on the managed heap were simple integral values like “0” and “1”. And since the fastest code is code you don’t need to execute at all, why bother allocating and formatting these small numbers over and over when we can instead just cache and reuse the results (effectively our own string interning pool)? That’s what PR dotnet/coreclr#18383 does, creating a small, specialized cache of the strings for “0” through “9”, and any time we now find ourselves formatting a single-digit integer primitive, we instead just grab the relevant string from this cache.

private int _digit = 4;

[Benchmark]
public string SingleDigitToString() => _digit.ToString();
Method Toolchain Mean Error StdDev Ratio Gen 0 Gen 1 Gen 2 Allocated
SingleDigitToString netcoreapp2.1 17.72 ns 0.3273 ns 0.3061 ns 1.00 0.0152 32 B
SingleDigitToString netcoreapp3.0 11.57 ns 0.1750 ns 0.1551 ns 0.65

Solution 2 - C#

The answer to the first question is because string equality isn't based on the object references, as reference types are by default.

first and third are both type string, even if only known at runtime, so the System.String's operator == override is called and:

> ...in turn, calls the static Equals(String, String) method, which performs an > ordinal (case-sensitive and culture-insensitive) comparison.

(source)

I'll also point out that Visual Studio provides a CS0253 compiler warning at first == second:

> Possible unintended reference comparison; to get a value comparison, > cast the right hand side to type 'string'

As for the second question... See @GuruStron's answer.

Categories

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionReza AriyanView Question on Stackoverflow
Solution 1 - C#Guru StronView Answer on Stackoverflow
Solution 2 - C#rfmodulatorView Answer on Stackoverflow