How To Use SubString In C#

String Substring Banner Image

What is Substring?

The substring function in C# is a useful string function for copying a chunk of an existing string. It returns a sub-section of the original string and depends on the selected index range. This is useful in string parsing with a repeatable pattern or fixed range. Substring is a great built-in function that can be the basis of doing a lot more.

Intro Video

How to Use Substring?

First Function Overload
    public string Substring(int startIndex)

The first one shown below with a single input gives the start index. It starts at 0. This is the simplest version. It copies from the given input start index and automatically extends the range to the end of the string which is length-1. This is useful if you know the beginning places where you want to copy and just get the rest of the string.

Second Function Overload
    public string Substring(int startIndex, int length)

The next function overload has two parameters. The start index is where the starting position for the substring and the length of the new substring. The substring length can not be longer than the remaining characters in the string. This is useful in dynamic settings where you may be searching for certain characters to determine the length to create a substring.

Example 1
String Index for User Name
012345678910
WilliamDoe

For example, if we have a user name William Doe and this string starts at 0 and ends at index 16. If We only wanted the last name then we give a start index of 7 from the below table. The start index will be the start of the new string that is created. Then we can get only the last name.

The following code will only get the last name, Doe.

    string userName = "William Doe";
    int startIndex = 8;
    string subUserName = userName.Substring(startIndex);
    Console.WriteLine($"subUserName:{subUserName}");

Output

    subUserName:Doe

This function does not modify the original string. String is a immutable. Meaning that it can be changed once it is created. See the following code example.

    string userName = "William Doe";
    int startIndex = 8;
    string subUserName = userName.Substring(startIndex);
    Console.WriteLine($"userName:{userName}");
    Console.WriteLine($"subUserName:{subUserName}");

Output

    userName:William Doe
    subUserName:Doe

Substring did not modify the variable userName as even after the function call it remains unchanged. Always keep in mind that any of the string functions will return a string and not modify the original.

012345678910
WilliamDoe
JohnDoe

Suppose that you had a list of names each where the space before the last name was at different positions then we can assume any more than the start index will be the same. We will find the space before the last name by using IndexOf. We could write our function to find it but it is better to find a csharp method that can do the job first than create our own. See below.

    List<string> userNames = new List<string>();
    userNames.Add("William Doe");
    userNames.Add("John Doe");
    foreach (string userName in userNames)
    {
        int startIndex = userName.IndexOf(' ');
        startIndex = startIndex + 1;
        string subUserName = userName.Substring(startIndex);
        Console.WriteLine($"subUserName:{subUserName}");
    }

Output

    subUserName:Doe
    subUserName:Doe

With this code, we can now find any last name that is in this format that has a space before the last name. As you may have noticed that the string function operations are useful together.

Why use the Substring Method?

Like many things in programming, there are multiple ways of doing the same thing. Let's explore some other ways we might go about getting the last name and why substring might be better for those reasons. For example, using the previous problem statement and we want to parse the user name and return only the last name. How might we go about doing that?

Character Loop

The string object can be looped through character by character. As we loop through we wait until we hit the space then save all the remaining characters to a string. Since in this case, we know the user names are not long saving it in a string is fine. But if the user names were long then it'd be better to save the contents in a StringBuilder object.

Output

    userName:William Doe
    subUserName:Doe

As you can see looping through each character is more code to write and generally, we would like to reduce code and improve readability.

IndexOf Function

We can find the position of the space before the last name by using the IndexOf function and then add one to find the beginning of the new substring. See below.

    string userName = "William Doe";
    //Find where the space right before the last name
    int startIndex = userName.IndexOf(' ');
    //Add to the start because want the start after the space
    startIndex = startIndex + 1; 
    string subUserName = userName.Substring(startIndex);
    Console.WriteLine($"subUserName:{subUserName}");

Output

    subUserName:Doe

This keeps the code simpler and more readable. C# in general has a lot of handy functions for handling the basic task for us to build upon and the string function set is an example of that.

What is Substring Best For?

I most often use substring when there are fixed indexes and lengths to copy characters from. It could be getting date info from a file name or some other metadata from a string such as an id number or variable. Take the below example of a file name where it has metadata separated by an underscore.

If given a file name with servername9349_5456453_02172022 represented by servername_processid_date. Where server name could have a range of characters between 1-10 characters, processid must be between 7 characters long and the date must be 8 characters long. This is a good use case to use Substring to copy the metadata out. Inspect the code below.

For writing any code I would think about the logic. I want to split the string by the underscore to get an array of strings where the array at index 0 will be the server name. The array at index 1 will be the processid and then the array at index 2 will hold the date. Once I have the date in the string then I separate the month, day, and year using a substring. See the code sample below.

    //Starting file name value
    string fileName = "servername9349_5456453_02172022";

    //Break file name into 3 parts
    string[] fileMetaData = fileName.Split('_');

    //Assign each of the new parts to new strings for readability
    string serverName = fileMetaData[0];
    string processId = fileMetaData[1];
    string dateRaw = fileMetaData[2];

    //Save substring values to a new string
    string monthRaw = dateRaw.Substring(0, 2);
    string dayRaw = dateRaw.Substring(2, 2);
    string yearRaw = dateRaw.Substring(4, 4);

    //Convert String values into int types
    int month = ConvertToInt(monthRaw);
    int day = ConvertToInt(dayRaw);
    int year = ConvertToInt(yearRaw);

    //Convert int values to a DateTime object
    DateTime dateTime = new DateTime(year, month, day);

    //Print out the remaining values
    Console.WriteLine($"serverName:{serverName}");
    Console.WriteLine($"processId:{processId}");
    Console.WriteLine($"dateTime:{dateTime.ToLongDateString()}");

    //Takes in raw value and tries to convert into an int else returns -1
    int ConvertToInt(string rawValue)
    {
        int value = 0;
        if(int.TryParse(rawValue, out value))
        {
            return value;
        }
        return -1;
    }

Output

    serverName: servername9349
    processId:5456453
    dateTime: Thursday, February 17, 2022

Substring Error Handling and Protection

The function can throw an exception in the following cases. If startIndex is less than 0 or if startIndex is greater than the string length - 1 then there is an out-of-bounds exception thrown. To add protection, before using the start index is best to check whether or not is less than 0 or greater than the size of the string.

    string SubStringWrapper(string userName, int startIndex)
    {
        if (startIndex < 0 || startIndex > userName.Length)
        {
            return "";
        }
        string subUserName = userName.Substring(startIndex);
        return subUserName;
    }

    //Proper use of substring so the correct last name is returned
    string userName = "William Doe";
    int startIndex = 8;
    string subUserName = SubStringWrapper(userName, startIndex);
    Console.WriteLine($"subUserName:{subUserName}");

    //Invalid start index less than 0 so it returns an empty string
    userName = "William Doe";
    startIndex = -39;
    subUserName = SubStringWrapper(userName, startIndex);
    Console.WriteLine($"subUserName2:{subUserName}");

    //Invalid start index that is greater than total string length so returns empty string
    userName = "William Doe";
    startIndex = 20;
    subUserName = SubStringWrapper(userName, startIndex);
    Console.WriteLine($"subUserName3:{subUserName}");

Output

    subUserName:Doe
    subUserName2:
    subUserName3:

Next, take look at the second function overload with protection. We have to protect against the upper bound of the string index.

    string SubStringWrapper(string userName, int startIndex, int length)
    {
        //We add basic bound protection on the startIndex less than 0 and great
        if (startIndex < 0 || startIndex > userName.Length)
        {
            return "";
        }

        //Check to see if the end bounds are greater than the string length itself
        if (startIndex + length > userName.Length)
        {
            return "";
        }
        string subUserName = userName.Substring(startIndex, length);
        return subUserName;
    }

    //Correct usage and will return the correct last name
    string userName = "William Doe";
    int startIndex = 8;

    //Total length of the new substring including the start index
    int length = 3;
    string subUserName = SubStringWrapper(userName, startIndex, length);
    Console.WriteLine($"subUserName1:{subUserName}");

    //Startindex is less than 0 so it returns an empty string
    startIndex = -1;
    length = 2;
    subUserName = SubStringWrapper(userName, startIndex, length);
    Console.WriteLine($"subUserName2:{subUserName}");
    startIndex = 8;

    //This will put the bounds over the upper limit of the index of the string so it returns an empty string
    length = 5;
    subUserName = SubStringWrapper(userName, startIndex, length);
    Console.WriteLine($"subUserName2:{subUserName}");

Output

    subUserName1:Doe
    subUserName2:
    subUserName2:

Additional Substring Examples

Analysis Of Getting First N Characters Methods

Best First Character Removal Function In A String

Get A Substring Before Or After A Certain Character

Get A Substring Between Two Strings

How Best To Get Last N Characters

How Does Substring Work?

Substring works by looping through each character from the start index given. Then it loops through the rest of the characters or has to stop after a certain number of characters. Inspect the code snippet below.

    using System.Text;
    string SubstringCustom(string userName, int startIndex)
    {
        if (userName.Length < 20)
        {
            string subUserName = "";
            for (int i = startIndex; i < userName.Length; i++)
            {
                subUserName += userName[i];
            }
            return subUserName;
        }
        else
        {
            StringBuilder sb = new StringBuilder();
            for (int i = startIndex; i < userName.Length; i++)
            {
                sb.Append(userName[i]);
            }
            return sb.ToString();
        }
    }
    string userName = "T Rex";
    int startIndex = userName.IndexOf(' ');
    startIndex = startIndex + 1;
    string subUserName = SubstringCustom(userName, startIndex);
    Console.WriteLine($"subUserName:{subUserName}");

Output

    subUserName:Rex

Notice that in cases where the length of the string is less than 20 then it just adds to another string but if it the over 20 then a StringBuilder object is used for performance reasons. Which is discussed more here.

Next, we'll look at how the second function works. With an additional parameter of length, it will be a similar process as the first function except that we will need to stop the substring at startIndex + length. Inspect the code below.

    using System.Text;
    string SubstringCustom(string userName, int startIndex, int length)
    {
        if (userName.Length < 20)
        {
            string subUserName = "";
            for (int i = startIndex; i < startIndex + length; i++)
            {
                subUserName += userName[i];
            }
            return subUserName;
        }
        else
        {
            StringBuilder sb = new StringBuilder();
            for (int i = startIndex; i < startIndex + length; i++)
            {
                sb.Append(userName[i]);
            }
            return sb.ToString();
        }
    }
    string userName = "Tree Hugger";
    int startIndex = userName.IndexOf(' ');
    startIndex = startIndex + 1;
    int length = userName.Length - startIndex;
    string subUserName = SubstringCustom(userName, startIndex, length);
    Console.WriteLine($"subUserName:{subUserName}");

Output

    subUserName:Hugger
Get Latest Updates