1

C#: Fastest Way To Check Last Character of a String

C#: Fastest Way To Check Last Character of a String

In this article we’ll benchmark in C#: Fastest Way To Check Last Character of a string.

There are plenty of applications and systems out there which require the need to check last character of a string. The perfect example seen all the time is needing to check if a product is “47A” versus “47C”.

That’s when this curious consultant started wondering what’s the fastest technique? Hence a showdown in C#: Fastest Way To Check Last Character of a string.

Setting the Stage:

I wrote a C# Console application to test multiple methods.

The code is written in Visual Studio 2015 targeting .Net Framework version 4.5 x64. The source code is available at the end of this blog so you can benchmark it on your own system.

Summarized, the code does the following:
1) Creates an array of strings of various lengths. The first time we create an array of 1,000 strings, then 100,000, then we go for broke at 10,000,000.

2) Loops through the array, comparing the last character of every string, using one of the methods below:

Technique

Code Snippet

String.EndsWith(“x”)

Accessing the character directly

String.Substring()

Enumerable.Last()

String.LastIndexOf()

Regex.IsMatch()

3) Every time a match is found, the total number of matches is incremented by 1. This verifies every loop ends up with the same total for each method.

The code assumes there will be no exceptions.

Turn’em loose!

Let’s see what happened on my machine.

All times are indicated in seconds.milliseconds format.

Lower numbers indicate faster performance. Winning times marked in green.

Run #1:

 


1,000


100,000


10,000,000

String.EndsWidth

00.0001932

00.0142231

01.4651393

String[String.Length – 1]

00.0000250

00.0012311

00.0976598

String.Substring

00.0001312

00.0118785

00.2248687

Enumeration.Last

00.0035442

00.0358329

01.4915060

String.LastIndexOf

00.0006875

00.0202248

01.8502749

Regex.IsMatch

00.0034079

00.0889541

05.3940095

 

Run #2:


 


1,000


100,000


10,000,000

String.EndsWidth

00.0002469

00.0139121

01.4538031

String[String.Length – 1]

00.0000258

00.0012385

00.1050509

String.Substring

00.0001271

00.0113140

00.2488104

Enumeration.Last

00.0037960

00.0462239

01.5123975

String.LastIndexOf

00.0006806

00.0446781

01.8581174

Regex.IsMatch

00.0033727

00.0898702

05.1968687

 

Run #3:

 


1,000


100,000


10,000,000

String.EndsWidth

00.0002006

00.0145041

01.4786785

String[String.Length – 1]

00.0000229

00.0012147

00.0846341

String.Substring

00.0000849

00.0110756

00.2632133

Enumeration.Last

00.0033727

00.0483466

01.4936676

String.LastIndexOf

00.0006071

00.0499634

01.8748690

Regex.IsMatch

00.0030038

00.1281857

05.3105023

 

One Method Kicked Ass

Look at the numbers. Like, just look at the numbers.

Treating the string as an “array” and accessing the character directly is significantly faster than anything else. Most of the time, by a factor of 10! :
string[string.length - 1] == "x"

Honestly, this should not come as a real surprise to developers micro-optimizing code because direct access will always be faster because there’s no additional allocations needed nor performed.

All the other contenders had respectible and similar performances. Except for Regex. Honestly, that should come as no real surprise either.

Final Say and What You Should Use:

When you have and feel the need for speed, using string[string.length - 1] == "x" is the only method you should be using, especially as the number of strings you need to check grows.

For easy code maintenance and readability, String.EndsWith(“x”) should suit most people just fine.

Regex has once again proved to be one of the slowest methods out there and for a simple scenario like this, shouldn’t even be considered, especially given the easy to read and understand String methods built into the .Net framework.

The Code:

  • Christiaan Rakowski

    No surprises, but nice to see the suspicions confirmed!

    In Regex’s defense, you are creating a new regex instance each time in your current test case and you can simply cache them in a dictionary. On my machine, this improves performance quite a bit, putting on par with the LastIndexOf solution.
    Still no where near as fast direct access, but perhaps people with a more complex/longer end pattern might find this useful.

    My addition:

    //Regex Cached —————————————————————-
    Console.WriteLine(“###########################################################”);
    Console.WriteLine(“Starting Regex Cached at: ” + DateTime.Now.ToLongTimeString());
    numberOfMatches = 0;
    var regexCache = new Dictionary();
    sw.Restart();
    for (int x = 0; x < s.Length; x++)
    {
    Regex regex;
    if (!regexCache.TryGetValue(EndCharToLookFor, out regex))
    {
    regex = new Regex(EndCharToLookFor + @"$");
    regexCache.Add(EndCharToLookFor, regex);
    }

    if (regex.IsMatch(s[x]))
    {
    numberOfMatches += 1;
    }
    }
    sw.Stop();
    Console.WriteLine("Finished at: " + DateTime.Now.ToLongTimeString());
    Console.WriteLine("Total matches: " + numberOfMatches.ToString("#,##0"));
    Console.WriteLine("Time to run: " + sw.Elapsed.ToString("mm\:ss\.fffffff"));

    Thread.Sleep(500);