flatbuffers icon indicating copy to clipboard operation
flatbuffers copied to clipboard

C# generated __lookup_by_key fails due to CompareTo instead of string.Compare(..., StringComparison.Ordinal)

Open timestee opened this issue 3 months ago • 1 comments

Description

I ran into an issue with the generated C# code for __lookup_by_key.

The current implementation of the binary search uses:

int comp = obj_.Key.CompareTo(key);

However, this fails to find elements even when the vector is sorted correctly.
For example, when looking up "OpenBox_point", the search fails despite the key being present in the table.

When I change the code to:

int comp = string.Compare(obj_.Key, key, StringComparison.Ordinal);

the lookup works as expected.


Steps to reproduce

  1. Create a FlatBuffers table with a sorted key vector containing mixed-case strings (e.g., "OpenBox_point").
  2. Use the generated C# __lookup_by_key to search for the key.
  3. Observe that CompareTo fails to find the entry.
  4. Replace CompareTo with string.Compare(..., StringComparison.Ordinal) and see that the search succeeds.

Expected behavior

__lookup_by_key should reliably find keys as long as the vector is sorted correctly, regardless of case differences or locale issues.


Actual behavior

The lookup fails because CompareTo does not behave the same as string.Compare(..., StringComparison.Ordinal).


Minimal Reproduction Code

class Program
{
    [Test]
    public void TestBinarySearch()
    {
        // Simulate a sorted key array. The keys are already sorted when the data is generated, using Go's string sort.
        string[] keys = {
            "30d_card_collection",
            "30d_card_defense",
            "30d_card_discount",
            "30d_card_dispatch",
            "30d_card_queue",
            "OpenBox_point",
            "allianceBoss_buff_upgrade_item",
            "alliance_box_1",
            "alliance_box_2",
            "alliance_box_3",
            "alliance_box_pay1",
            "alliance_box_pay2",
            "alliance_box_pay3",
            "alliance_box_pay4",
            "alliance_box_pay5",
            "alliance_box_pay6",
            "alliance_box_pay7",
            "alliance_points_2500"
        };

        // Generated FlatBuffers __lookup_by_key logic (uses CompareTo)
        string target = "OpenBox_point";
        int idx = BinarySearch(keys, target);
        Console.WriteLine($"Search result with CompareTo: {idx}"); // ❌ Not found

        // Modified version using StringComparison.Ordinal
        idx = BinarySearchOrdinal(keys, target);
        Console.WriteLine($"Search result with Ordinal: {idx}");   // ✅ Found correctly
    }

    static int BinarySearch(string[] arr, string key)
    {
        int start = 0;
        int span = arr.Length;

        while (span != 0)
        {
            int middle = span / 2;
            int comp = arr[start + middle].CompareTo(key);
            if (comp > 0)
            {
                span = middle;
            }
            else if (comp < 0)
            {
                middle++;
                start += middle;
                span -= middle;
            }
            else
            {
                return start + middle;
            }
        }
        return -1;
    }

    static int BinarySearchOrdinal(string[] arr, string key)
    {
        int start = 0;
        int span = arr.Length;

        while (span != 0)
        {
            int middle = span / 2;
            int comp = string.Compare(arr[start + middle], key, StringComparison.Ordinal);
            if (comp > 0)
            {
                span = middle;
            }
            else if (comp < 0)
            {
                middle++;
                start += middle;
                span -= middle;
            }
            else
            {
                return start + middle;
            }
        }
        return -1;
    }
}

Search result with CompareTo: -1
Search result with Ordinal: 5

Question

Is this the intended behavior for the C# code generator, or is this a bug?
It seems that using string.Compare with StringComparison.Ordinal would be more reliable and consistent with FlatBuffers' cross-platform guarantees.

Thanks!

timestee avatar Sep 06 '25 13:09 timestee

I have hundreds of tables that were automatically converted from Protobuf. I’ve already written a script to perform the replacements automatically, and so far everything seems to be working fine.

timestee avatar Sep 09 '25 01:09 timestee

I believe this is solved by #8547, and this is a duplicate of #8553, so closing as such.

jtdavis777 avatar Dec 06 '25 05:12 jtdavis777