Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

C# String Extensions

4.86/5 (12 votes)
5 Dec 2013CPOL2 min read 63.2K   1K  
Some commonly used operations with string in c# as extension methods

Introduction  

It is common we C# developers most of the time manipulate string values in our program for various reasons especially when it comes from user input, database or files etc. For example, we convert a string to integer when a user fills age value in a text box. The String class offers ConvertXxx methods for conversions like this but it lacks in flexibility and control, I think .  I have just consolidated all the extensions method I wrote against String class depends on the requirement at various times. Sure enough there are different ways you can achieve what I did using extension methods but I hope you will find them easy and extensible. 

Background  

Using built-in string methods can help us get the desired result but you repeat the code everywhere in need and I felt isolating the logic to one place will help the logic maintainable and testable.

Using the code

There are only two classes you can look at in the source code attached here : StringExtensions and StringExtensionsTest.The first class has all the extension methods for string class and the second class will test each extension methods with different scenarios to ensure the desired output returned to the user. 


These are the 13 extension methods.

Image 1

We will look at each method and few test methods to verify the result (there are 59 test methods in total which you can look at it by downloading the source code) 

Note : I named the test method as Roy Osherove suggested in his book The art of Unit Testing. That is [methodUnderTest]_[Scenario]_[Expected].

Example

C#
public void toDate_hasValidDate_Date()

Extension methods

1.toDate

Accepts a string and convert as DateTime. It has an optional parameter throwExceptionIfFailed. if it is true then caller of this method should expect exception and handle it. Default is false which means that it returns DateTime.MinValue if the string cannot be converted as DateTime

C#
public static DateTime toDate(this string input, bool throwExceptionIfFailed = false)
{
    DateTime result;
    var valid = DateTime.TryParse(input, out result);
    if (!valid)
        if (throwExceptionIfFailed)
            throw new FormatException(string.Format("'{0}' cannot be converted as DateTime", input));
    return result;
}

And the test methods:

C#
[TestMethod]
public void toDate_hasValidDate_Date()
{
    Assert.AreEqual(DateTime.Parse("2013-12-05"), "2013-12-05".toDate());
}

[TestMethod]
public void toDate_hasInvalidDate_MinDate()
{
    Assert.AreEqual(DateTime.MinValue, "2013-50-70".toDate());
}
[TestMethod]
[ExpectedException(typeof(FormatException))]
public void toDate_notADateStringButWantException_FormatException()
{
    "abcd".toDate(true);
}

2.toInt

C#
public static int toInt(this string input, bool throwExceptionIfFailed = false)
{
    int result;
    var valid = int.TryParse(input, out result);
    if (!valid)
        if (throwExceptionIfFailed)
            throw new FormatException(string.Format("'{0}' cannot be converted as int", input));
    return result;
}

And the test methods

C#
[TestMethod]
public void toInt_notANumber_0()
{
    Assert.AreEqual(0, "abcd".toInt());
}

[TestMethod]
public void toInt_10_10()
{
    Assert.AreEqual(10, "10".toInt());
}

[TestMethod]
public void toInt_alphaNumeric_0()
{
    Assert.AreEqual(0, "a10b34c".toInt());
}

3.toDouble

C#
public static double toDouble(this string input, bool throwExceptionIfFailed = false)
{
    double result;
    var valid = double.TryParse(input, NumberStyles.AllowDecimalPoint, 
      new NumberFormatInfo { NumberDecimalSeparator = "." }, out result);
    if (!valid)
        if (throwExceptionIfFailed)
            throw new FormatException(string.Format("'{0}' cannot be converted as double", input));
    return result;
}

And the test methods

C#
[TestMethod]
public void toDouble_notANumber_0()
{
    Assert.AreEqual(0, "abcd".toDouble());
}

[TestMethod]
public void toDouble_10point5_10point5()
{
    Assert.AreEqual(10.5, "10.5".toDouble());
}

4.reverse

C#
public static string reverse(this string input)
{
    if (string.IsNullOrWhiteSpace(input)) return string.Empty;
    char[] chars = input.ToCharArray();
    Array.Reverse(chars);
    return new String(chars);
}

And the test methods

C#
[TestMethod]
public void reverse_empty_empty()
{
    Assert.AreEqual("", "".reverse());
}

[TestMethod]
public void reverse_abcd_dcba()
{
    Assert.AreEqual("dcba", "abcd".reverse());
}

5.toSentence

C#
/// <summary>
/// Matching all capital letters in the input and seperate them with spaces to form a sentence.
/// If the input is an abbreviation text, no space will be added and returns the same input.
/// </summary>
/// <example>
/// input : HelloWorld
/// output : Hello World
/// </example>
/// <example>
/// input : BBC
/// output : BBC
/// </example>
/// <param name="input" />
/// <returns>
public static string toSentence(this string input)
{
    if (string.IsNullOrWhiteSpace(input))
        return input;
    //return as is if the input is just an abbreviation
    if (Regex.Match(input, "[0-9A-Z]+$").Success)
        return input;
    //add a space before each capital letter, but not the first one.
    var result = Regex.Replace(input, "(\\B[A-Z])", " $1");
    return result;
}

And the test methods

C#
[TestMethod]
public void toSentence_OneCapLetter_OneSpace()
{
    Assert.AreEqual("Hello World", "HelloWorld".toSentence());
}

[TestMethod]
public void toSentence_5CapLetters_5Spaces()
{
    Assert.AreEqual("Such A Long Text To Test", 
      "SuchALongTextToTest".toSentence());
}

[TestMethod]
public void toSentence_NoCapLetter_AsIs()
{
    Assert.AreEqual("test", "test".toSentence());
}

One sample usage of this method. Consider this Enum and you print them using this extension

C#
enum Task
{
    Code,
    Test,
    BugFix,
    ProdDep,
    DbDesign,
    ScreenDesign,
    TDD
}
C#
var tasks = Enum.GetValues(typeof(Task));
foreach (var task in tasks)
{
    Console.WriteLine(task.ToString().toSentence());
}

Result

C#
Code
Test
Bug Fix
Prod Dep
Db Design
Screen Design
TDD

6.getLast

C#
public static string getLast(this string input, int howMany)
{
    if (string.IsNullOrWhiteSpace(input)) return string.Empty;
    var value = input.Trim();
    return howMany >= value.Length ? value : value.Substring(value.Length - howMany);
}

And the test methods

C#
[TestMethod]
public void getLast_2CharectersButAskFor3_2charecter()
{
    Assert.AreEqual("ab", "ab".getLast(3));
}
[TestMethod]
public void getLast_2CharectersAndAskFor2_2charecter()
{
    Assert.AreEqual("ab", "ab".getLast(2));
}
[TestMethod]
public void getLast_4CharectersAndAskFor2_last2charecter()
{
    Assert.AreEqual("cd", "abcd".getLast(2));
}

7.getFirst

C#
public static string getFirst(this string input, int howMany)
{
    if (string.IsNullOrWhiteSpace(input)) return string.Empty;
    var value = input.Trim();
    return howMany >= value.Length ? value : input.Substring(0, howMany);
}

And the test methods

C#
[TestMethod]
public void getFirst_2CharectersButAskFor3_2charecter()
{
    Assert.AreEqual("ab", "ab".getFirst(3));
}
[TestMethod]
public void getFirst_2CharectersAndAskFor2_2charecter()
{
    Assert.AreEqual("ab", "ab".getFirst(2));
}
[TestMethod]
public void getFirst_4CharectersAndAskFor2_first2charecter()
{
    Assert.AreEqual("ab", "abcd".getFirst(2));
}

8.isEmail

C#
public static bool isEmail(this string input)
{
    var match = Regex.Match(input, 
      @"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", RegexOptions.IgnoreCase);
    return match.Success;
}

And the test methods

C#
[TestMethod]
public void isEmail_notEmail_false()
{
    Assert.AreEqual(false, "1234".isEmail());
}

[TestMethod]
public void isEmail_mail_true()
{
    Assert.AreEqual(true, "abc@abc.com".isEmail());
} 

9.isPhone

C#
public static bool isPhone(this string input)
{
    var match = Regex.Match(input, 
      @"^\+?(\d[\d-. ]+)?(\([\d-. ]+\))?[\d-. ]+\d$", RegexOptions.IgnoreCase);
    return match.Success;
} 

And the test methods

C#
[TestMethod]
public void isPhone_ValidNumberPrefixedPlus_true()
{
    Assert.AreEqual(true, "+1234".isPhone());
}
[TestMethod]
public void isPhone_ValidNumberPrefixedTwoPlus_false()
{
    Assert.AreEqual(false, "++1234".isPhone());
} 

10.isNumber

C#
public static bool isNumber(this string input)
{
    var match = Regex.Match(input, @"^[0-9]+$", RegexOptions.IgnoreCase);
    return match.Success;
}  

And the test methods

C#
Sorry, no test methods! task for you:) 

11.extractNumber

C#
        public static int extractNumber(this string input)
{
    if (string.IsNullOrWhiteSpace(input)) return 0;

    var match = Regex.Match(input, "[0-9]+", RegexOptions.IgnoreCase);
    return match.Success ? match.Value.toInt() : 0;
} 

And the test methods

C#
[TestMethod]
public void extractNumber_TextWith100_100()
{
    Assert.AreEqual(100, "in 100 between".extractNumber());
}

[TestMethod]
public void extractNumber_100_100()
{
    Assert.AreEqual(100, "100".extractNumber());
}

[TestMethod]
public void extractNumber_TextWithMixedNumbers_firstNumber()
{
    Assert.AreEqual(100, "first 100 then 60 then 8 then24".extractNumber());
} 

12.extractEmail

C#
public static string extractEmail(this string input)
{
    if (input == null || string.IsNullOrWhiteSpace(input)) return string.Empty;

    var match = Regex.Match(input, @"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", RegexOptions.IgnoreCase);
    return match.Success ? match.Value : string.Empty;
}  

And the test methods

C#
[TestMethod]
public void extractEmail_containsEmail_Email()
{
    Assert.AreEqual("email@email.com", "name,+86738238;email@email.com;address".extractEmail());
}

[TestMethod]
public void extractEmail_noEmail_empty()
{
    Assert.AreEqual("", "just some text".extractEmail());
}

[TestMethod]
public void extractEmail_Email_Email()
{
    Assert.AreEqual("abc@abc.com", "abc@abc.com".extractEmail());
}  

13.extractQueryStringParamValue

C#
public static string extractQueryStringParamValue(this string queryString, string paramName)
{
    if (string.IsNullOrWhiteSpace(queryString) || string.IsNullOrWhiteSpace(paramName)) return string.Empty;

    var query = queryString.Replace("?", "");
    if (!query.Contains("=")) return string.Empty;
    var queryValues = query.Split('&').Select(piQ => piQ.Split('=')).ToDictionary(
      piKey => piKey[0].ToLower().Trim(), piValue => piValue[1]);
    string result;
    var found = queryValues.TryGetValue(paramName.ToLower().Trim(), out result);
    return found ? result : string.Empty;
}

And the test methods

C#
[TestMethod]
public void extractQueryStringParamValue_twoParamsWantFirst_first()
{
    Assert.AreEqual("One", 
      "?param1=OneĀ¶m2=two".extractQueryStringParamValue("param1"));
}

[TestMethod]
public void extractQueryStringParamValue_invalidQueryString_empty()
{
    Assert.AreEqual("", "justfun".extractQueryStringParamValue("param1"));
}

Points of Interest

You can add/modify scenarios to the test methods to get desired output based on your requirement.For example in isPhone, you can put restriction on min and max length.Subsequently you change the extension method if the test method fails.

History

Initial version as on 5-Dec-2013

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)