5
\$\begingroup\$

I apologize if this isn't fit for this site but it is a puzzle, not all that practical, so I thought it would fit.

Take this code:

string s = "";
Console.WriteLine(ReferenceEquals(s, string.Empty));

This will print out true because "" is interned by the JITter at some early point and literals are reused from the interning cache.

The question and puzzle is this:

How can you initialize s such that
1. s.Length == 0
2. ReferenceEquals(s, string.Empty) == false

I've tried the following:

string s = "test".Substring(0, 0); // any arbitrary legal start offset tried
string s = new string(' ', 0);
string s = new string(new char[0]);
string s = CallSomeMethodThatWontBeInlined() + CallSomeMethodThatWontBeInlined();

These three all end up being equal to string.Empty.

Bonus if you can explain the above behavior with regards to new string(...). I have my thoughts about it but no solid evidence one way or another.

I can understand that all methods that concatenate or take substrings of existing strings special-case the result that will end up with length zero, but how new string(...) actually doesn't create a new object I don't know.

Note! I'm asking for legal C#. I'm not asking for a p/invoke or assembly code call that just manually constructs the bytes of an empty string object in memory and stuffs a pointer to it into a string variable.

Any method or path through existing classes in .NET or using operators of strings or whatnot that would end up with the required results are legal. As such, take the Substring example above, if that returned a new empty string that isn't the same as the existing one, then that would be a/the answer to this puzzle.

Reflection is boundary, at the very least it has to be reflection that can run in untrusted scenarios.


My own "conclusion" is that this may indeed be impossible because the actual C# code that would end up constructing such a string would end up with one of the new string(...) overloads, or using one of the operators which all seem to handle this cases. As such, the end-point of any such code may be the one that handles this case and since it does, it may not be possible.

\$\endgroup\$
6
  • \$\begingroup\$ I guess without some IL this might be impossible. You should also say drop the legal .NET code, because IL is .NET. Otherwise I'd use C++/CLI or any other .NET language. If you only want C#, say C# \$\endgroup\$ Commented Nov 10, 2016 at 11:15
  • \$\begingroup\$ OK, I dropped the bit about .NET. \$\endgroup\$ Commented Nov 10, 2016 at 11:16
  • \$\begingroup\$ For your own research, you can try to disassemble the .NET library and look at the source of the string class. \$\endgroup\$ Commented Nov 10, 2016 at 11:20
  • 1
    \$\begingroup\$ .NET is now largely open sourced on github, the string C# source is here - github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/… - and the native methods are here - github.com/dotnet/coreclr/blob/master/src/classlibnative/… - but most of the native code is greek to me :) \$\endgroup\$ Commented Nov 10, 2016 at 11:31
  • \$\begingroup\$ In my opinion if you disallow reflection as well you're pretty much asking people to find a bug. \$\endgroup\$ Commented Nov 10, 2016 at 11:51

2 Answers 2

3
\$\begingroup\$

So, I'll take the easy way:

string s = "";

So, s.Length is now 0, so far so good. But how can we get ReferenceEquals(s, string.Empty) to false.

Piece of cake, just put that into your class:

public static new bool ReferenceEquals(object a, object b) => false;

and the above call will result in false.

I will look for some more sneaky methods, so.

\$\endgroup\$
2
2
\$\begingroup\$

1st try: Reflection

String s = "abc";
PropertyInfo pi = s.GetType().GetProperty("Length");
pi.SetValue(s, 0, null);

Yields error, there is no set property

Loophole

var s = new { Length = 0 };

Now the ReferenceEquals will return false and s.Length == 0. Just sticking to your rules =)

\$\endgroup\$
3
  • \$\begingroup\$ Another devious circumvention :) \$\endgroup\$ Commented Nov 10, 2016 at 12:12
  • \$\begingroup\$ The question asked how to initialise s, but it didn't say that you could also change the type of s. This is either answering a completely different question or interpreting the challenge too literally, which is against the site rules. \$\endgroup\$ Commented Nov 10, 2016 at 14:46
  • \$\begingroup\$ After many years of learning: You need to access m_stringLength by var fi = s.GetType().GetField("m_stringLength", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); now you can set the internal m_stringLength to 0 by using fi.SetValue(s, 0); now s.Length is equal to 0 and object.ReferenceEquals(s, string.Empty) is false \$\endgroup\$ Commented Nov 12, 2021 at 16:27

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.