InternedString as alternative to regular String I need to use large strings as a dictionary keys, and I want to optimize the repetitive GetHashCode() and Equals() calls in it.

The number of keys will be quite small (&lt;1000), but each string length will be very big (it's generated SQL queries cache), and I expect a lot of hot lookups.

My idea is to make sure that each wrapped string is interned, so that we can rely on the string references of the same strings to be always the same:

public sealed class InternedString : IEquatable&lt;InternedString&gt;
{
    public InternedString(string s) =&gt; String = string.Intern(s);

    public string String { get; }

    public override bool Equals(object obj) =&gt; String.Equals(obj);

    public bool Equals(InternedString other) =&gt; String.Equals(other?.String);

    public override int GetHashCode() =&gt; RuntimeHelpers.GetHashCode(String);

    public static bool operator ==(InternedString l, InternedString r) =&gt;
        l?.String == r?.String;

    public static bool operator !=(InternedString l, InternedString r) =&gt; !(l == r);
}


Can you review this class, with special attention to safety?

UPDATE

You can check the final code and benchmarks here: https://github.com/astef/InternedString

Usage:

// O(n) operation happens only here, so we want to re-use this object
var iString = new InternedString("typically_a_very_long_string_key");

// now any call to `GetHashCode()` and `Equals(...)` will run in a constant time
mySet.Add(iString);
Assuming each time you receive a new query you create an instance of InternedString to get/add another item from/into the dictionary, the performance benefit seems questionable:


Impact of string.Intern


a) It needs to calculate a hash of the original string to make a lookup in the pool of strings 

b) From official documentation - 


  The memory allocated for interned String objects is not likely to be
  released until the common language runtime (CLR) terminates. The
  reason is that the CLR's reference to the interned String object can
  persist after your application, or even your application domain,
  terminates.



High rate of collisions seems to be the only beneficial scenario


The equality comparison that Dictionary does for items in the bucket is the case when you implementation might win when the bucket grows huge.

But do you really have that many collisions?

P.S.

Comparing strings you're not using String.ReferenceEquals to compare references of the interned strings, but rather == operator which for (sting vs string) leads to values comparison.