/// <summary>
        /// Adds a string to the set if it does not already exist.
        /// </summary>
        /// <param name="chars">A pointer to the string you want to add.</param>
        /// <param name="count">The length of the string (in chars).</param>
        /// <param name="str">The string object representation of the characters. A new string is only allocated when it does not already exist in the set.</param>
        /// <param name="knownHashValue">(optional) If the StringHash has already been calculated, you can provide it here to save re-calculation.</param>
        /// <returns>True if the string was added. False if the string already existed in the set.</returns>
        public unsafe bool Add(char *chars, int count, out string str, StringHash knownHashValue = default(StringHash))
        {
            if (knownHashValue == default(StringHash))
            {
                knownHashValue = StringHash.GetHash(chars, count);
            }

            str = GetExistingString(chars, count, knownHashValue);

            if (str != null)
            {
                return(false); // didn't add anything
            }
            // an existing string wasn't found, we need to add it to the hash
            lock (_writeLock)
            {
                // first, check one more time to see if it exists
                str = GetExistingString(chars, count, knownHashValue);

                if (str == null)
                {
                    // it definitely doesn't exist. Let's add it
                    str = new string(chars, 0, count);
                    AddImpl(str, knownHashValue);
                    return(true);
                }

                return(false);
            }
        }
        void AddImpl(string s, StringHash hash)
        {
            var data = _data;

            if (data.NextAvailableSlotIndex == data.Slots.Length)
            {
                Grow();
                data = _data;
            }

            var slots   = data.Slots;
            var buckets = data.Buckets;

            var bucket    = hash.Value % slots.Length;
            var slotIndex = data.NextAvailableSlotIndex;

            data.NextAvailableSlotIndex++;

            slots[slotIndex].Value    = s;
            slots[slotIndex].HashCode = hash;
            slots[slotIndex].Next     = buckets[bucket] - 1;

            // The hash set would no longer be thread-safe on reads if somehow the bucket got reassigned before the slot was setup
            Thread.MemoryBarrier();

            buckets[bucket] = slotIndex + 1;
        }
        /// <summary>
        /// Adds a string to the set if it does not already exist.
        /// </summary>
        /// <param name="buffer">The character array which represents the string you want to add.</param>
        /// <param name="start">The index in the character array where your string starts.</param>
        /// <param name="count">The length of the string you want to add.</param>
        /// <param name="str">The string object representation of the characters. A new string is only allocated when it does not already exist in the set.</param>
        /// <param name="knownHashValue">(optional) If the StringHash has already been calculated, you can provide it here to save re-calculation.</param>
        /// <returns>True if the string was added. False if the string already existed in the set.</returns>
        public bool Add(char[] buffer, int start, int count, out string str, StringHash knownHashValue = default(StringHash))
        {
            if (knownHashValue == default(StringHash))
            {
                knownHashValue = StringHash.GetHash(buffer, start, count);
            }
            else
            {
                StringHash.AssertBufferArgumentsAreSane(buffer.Length, start, count);
            }

            str = GetExistingStringImpl(buffer, start, count, knownHashValue);

            if (str != null)
            {
                return(false); // didn't add anything
            }
            // an existing string wasn't found, we need to add it to the hash
            lock (_writeLock)
            {
                // first, check one more time to see if it exists
                str = GetExistingStringImpl(buffer, start, count, knownHashValue);

                if (str == null)
                {
                    // it definitely doesn't exist. Let's add it
                    str = new string(buffer, start, count);
                    AddImpl(str, knownHashValue);
                    return(true);
                }

                return(false);
            }
        }
        /// <summary>
        /// Uses the characters from a buffer to check whether a string exists in the set, and retrieve it if so.
        /// </summary>
        /// <param name="buffer">The character array which represents the string you want to check for.</param>
        /// <param name="start">The index in the character array where your string starts.</param>
        /// <param name="count">The length of the string you want to check for.</param>
        /// <param name="knownHashValue">(optional) If the StringHash has already been calculated, you can provide it here to save re-calculation.</param>
        /// <returns>If found in the set, the existing string is returned. If not found, null is returned.</returns>
        public string GetExistingString(char[] buffer, int start, int count, StringHash knownHashValue = default(StringHash))
        {
            if (knownHashValue == default(StringHash))
            {
                knownHashValue = StringHash.GetHash(buffer, start, count);
            }
            else
            {
                StringHash.AssertBufferArgumentsAreSane(buffer.Length, start, count);
            }

            return(GetExistingStringImpl(buffer, start, count, knownHashValue));
        }
        bool ContainsString(string str, StringHash hash)
        {
            var cursor = GetSearchCursor(hash);

            while (cursor.MightHaveMore)
            {
                if (str == cursor.NextString())
                {
                    return(true);
                }
            }

            return(false);
        }
        string GetExistingStringImpl(char[] buffer, int start, int length, StringHash hash)
        {
            var cursor = GetSearchCursor(hash);

            while (cursor.MightHaveMore)
            {
                var value = cursor.NextString();
                if (value != null && UnsafeStringComparer.AreEqual(value, buffer, start, length))
                {
                    return(value);
                }
            }

            return(null);
        }
        /// <summary>
        /// Returns a search cursor which allows you to iterate over every string in the set with the same StringHash.
        /// </summary>
        public StringSearchCursor GetSearchCursor(StringHash hash)
        {
            var data   = _data;
            var cursor = new StringSearchCursor();

            cursor.Slots = data.Slots;
            cursor.Hash  = hash;

            var buckets = data.Buckets;
            var bucket  = hash.Value % buckets.Length;

            cursor.SlotIndex = buckets[bucket] - 1;

            return(cursor);
        }
        /// <summary>
        /// Uses the characters from a buffer to check whether a string exists in the set, and retrieve it if so.
        /// </summary>
        /// <param name="chars">A pointer to the string to search for.</param>
        /// <param name="count">The length of the string (in chars).</param>
        /// <param name="knownHashValue">(optional) If the StringHash has already been calculated, you can provide it here to save re-calculation.</param>
        /// <returns>If found in the set, the existing string is returned. If not found, null is returned.</returns>
        public unsafe string GetExistingString(char *chars, int count, StringHash knownHashValue = default(StringHash))
        {
            if (knownHashValue == default(StringHash))
            {
                knownHashValue = StringHash.GetHash(chars, count);
            }

            var cursor = GetSearchCursor(knownHashValue);

            while (cursor.MightHaveMore)
            {
                var value = cursor.NextString();
                if (value != null && UnsafeStringComparer.AreEqual(value, chars, count))
                {
                    return(value);
                }
            }

            return(null);
        }
        /// <summary>
        /// Adds a string to the set if it does not already exist.
        /// </summary>
        /// <param name="str">The string to add to the set.</param>
        /// <param name="knownHashValue">(optional) If the StringHash for str has already been calculated, you can provide it here to save re-calculation.</param>
        /// <returns>True if the string was added. False if the string already existed in the set.</returns>
        public bool Add(string str, StringHash knownHashValue = default(StringHash))
        {
            if (knownHashValue == default(StringHash))
            {
                knownHashValue = StringHash.GetHash(str);
            }

            if (!ContainsString(str, knownHashValue))
            {
                lock (_writeLock)
                {
                    if (!ContainsString(str, knownHashValue))
                    {
                        AddImpl(str, knownHashValue);
                        return(true);
                    }
                }
            }

            return(false);
        }