Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is String.Equals(string1.Substring(0, x), string2) better than string1.StartsWith(string2)?

Tags:

c#

.net

I am using string comparisons to test URL paths using StringComparison.OrdinalIgnoreCase.

MSDN gives the following string comparison advice HERE, but does not clarify WHY:

MSDN Example (half-way down the above page):

public static bool IsFileURI(string path) 
{
   path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
   return true;
}

MSDN Advice:

"However, the preceding example uses the String.StartsWith(String, StringComparison) method to test for equality. Because the purpose of the comparison is to test for equality instead of ordering the strings, a better alternative is to call the Equals method, as shown in the following example."

public static bool IsFileURI(string path)
{
   if (path.Length < 5) return false;

   return String.Equals(path.Substring(0, 5), "FILE:", 
                    StringComparison.OrdinalIgnoreCase);
}

QUESTION: Why does MSDN suggest the second example is better?

Discussion points:

  1. Clearly the return true; in the first example is a bug and should be return path.StartsWith(...);. We can safely ignore this as a bug as the VB code is correct.

  2. Creation of a substring prior to comparing for equality would appear to only use another memory resource than just calling String.StartsWith().

  3. The Length < 5 test is a nice short-circuit, however it could be used with the prior code just the same.

  4. The second example could be construed as clearer code, but I am concerned with performance. The creation of the substring seems unnecessary.

like image 762
Kevin P. Rice Avatar asked Jan 15 '12 05:01

Kevin P. Rice


1 Answers

Looking at the StartsWith method using dotPeek, it eventually calls an internal comparison function that compares the entire string, and returns a boolean result based on the return value of that comparison:

return TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0;

String.Equals calls:

return TextInfo.CompareOrdinalIgnoreCase(this, value) == 0;

CompareOrdinalIgnoreCase calls a private method, which dotPeek doesn't show, but my hunch is that the overload called by StartsWith traverses the entire string while the overload called by Equals stops as soon as equality can be determined.

If performance is a concern, try measuring both with values that will be typical for your application.


Out of curiousity, I tried measuring the two, and it does seem that Equals is noticeably faster. When I run the code below using a release build, Equals is nearly twice as fast as StartsWith:

using System;
using System.Diagnostics;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var url = "http://stackoverflow.com/questions/8867710/is-string-equalsstring1-substring0-x-string2-better-than-string1-startswit";
            var count = 10000000;
            var http = false;

            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < count; i++)
            {
                http = url.StartsWith("http:", StringComparison.OrdinalIgnoreCase);
            }

            sw.Stop();

            Console.WriteLine("StartsWith: {0} ms", sw.ElapsedMilliseconds);

            sw.Restart();

            for (int i = 0; i < count; i++)
            {
                http = string.Equals(url.Substring(0, 5), "http:", StringComparison.OrdinalIgnoreCase);
            }

            sw.Stop();

            Console.WriteLine("Equals: {0} ms", sw.ElapsedMilliseconds);

            Console.ReadLine();
        }
    }
}
like image 137
Jeff Ogata Avatar answered Nov 13 '22 08:11

Jeff Ogata