2

In C# 4.0, whenever I compare two strings which have one or more trailing slashes, the comparison gives incorrect results:

String a = "1/2.1/";
String b = "1/2/";
if (a.CompareTo(b) > 0)
    MessageBox.Show("Correct: " + a + " > " + b);
else
    MessageBox.Show("Not correct: " + a + " <= " + b);

a = a.TrimEnd('/');
b = b.TrimEnd('/');

if (a.CompareTo(b) > 0)
    MessageBox.Show("Trailing slash removed. Correct: " + a + " > " + b);
else
    MessageBox.Show("Trailing slash removed. Not correct: " + a + " <= " + b);

Lexically speaking, "1/2.1/" comes after "1/2/", and there is not much question about that.

This behaviour also occurs in other places, such as sorting a datatable using the Select method.

Am I doing something wrong? Or is this a bug in .Net? It should not even have anything to do with culture-specific information etc. since a slash is part of the most basic US ASCII character set.

I am running into this when comparing SQL Server hierarchyIDs. It's easy enough to solve but it is a somewhat astonishing problem.

3
  • 2
    . is 0x2e, / is 0x2f. . comes first. Your problem is exactly in the assumption you took for granted: "1/2.1/ comes after 1/2/, and there is not much question about that." Commented Apr 12, 2015 at 12:49
  • 2
    Note, that you are using culture sensitive comparison. Few people understand the Unicode comparison rules. There are many behaviors that you will find surprising. Commented Apr 12, 2015 at 12:56
  • See also Compare version numbers without using split function Commented Apr 13, 2015 at 8:49

3 Answers 3

1

Lexically speaking, "1/2.1/" comes after "1/2/", and there is not much question about that.

Why would it come after? On the ASCII chart, the / comes immediately after the ..

Given the following two strings, they're equal until you reach the 4th character. Then the / and . are compared, and the / is greater. So the result you're seeing (a < b) is actually correct.

1/2.1/
1/2/

After calling TrimEnd(), you end up with two different strings where and a > b.

1/2.1
1/2
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks - of course you are right, C# performs a character-by-character code point comparison. Got tripped up by the common claim that SQL Server hierarchyID (string-converted) values are strictly in sequence - this is where these type of strings originates. Well, they are not in sequence then.
This will work in the case where the strings are longer: 1/2.1/1/ and 1/2/1/ or if their first number differs: 1.1/2/ and 1/2/.
1

If my old skill in C doesn't fail me, I think that CompareTo executes a character by character subtraction of the Integer value of the characters until the result is not zero.

After the first 3 identical characters the CompareTo looks at the fourth character, and this is a point for the first string and a slash for the second string.

The integer value of the point character is 46 while the integer value of the slash is 47, 46-47 gives back -1 so "1/2.1/" is less than "1/2/".

Comments

1

You can compare strings containing numbers if the numbers are right aligned:

01/02.00/
01/02.10/
01/10.00/

If this is not possible, consider creating a type for your numbers

public class ChapterNumber :  IComparable<ChapterNumber>
{
    private readonly decimal[] _number;

    public ChapterNumber(params decimal[] number)
    {
        _number = number;
    }

    public int CompareTo(T obj)
    {
        var other = obj as ChapterNumber;
        if (other == null) {
            return +1;
        }
        int len = Math.Min(_number.Length, other._number.Length);
        for (int i = 0; i < len; i++) {
            int result = _number[i].CompareTo(other._number[i]);
            if (result != 0) {
                return result;
            }
        }
        return _number.Length.CompareTo(other._number.Length);
    }

    public override ToString()
    {
        return String.Join('/', _number) + "/";
    }
}

Usage:

var a = new ChapterNumber(1, 2.1m);
var b = new ChapterNumber(1, 2);
if (a.CompareTo(b) > 0) {
    ...
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.