A group of useful extensions in C#, supporting .NET 6.0 and .NET 7.0
I found myself creating useful extensions over and over as I moved along my career path and decided that they actually needed a home to be reusable and grow. They are not trade secrets or proprietary code they are just little bits of code that are useful.
SafeGetElement - Gets an element from an array, without throwing an exception if the index is out of bounds.
e.g.
var intArray = new[] { 2, 4, 8, 1, 7 };
var itemValue = intArray.SafeGetElement(2);
itemValue will be 8.
e.g.
var intArray = new[] { 2, 4, 8, 1, 7 };
var itemValue = intArray.SafeGetElement(20, 1);
itemValue will be 1, as the array does not contain 20 elements.
e.g.
var intArray = new[] { 2, 4, 8, 1, 7 };
var itemValue = intArray.SafeGetElement(1, 1, (s) => s * 2);
itemValue
will be 8, as the element at location 1, is 4, which is then multiplied by 2.
The test project has more examples using strings and classes.
EqualTo - A simple comparison of characters including an option to compare by the case (default is to ignore case)
e.g.
'c'.EqualTo('C');
or
'c'.EqualTo('C', StringComparison.Ordinal);
ContainsValue (same as HasValue) - Looks for a specified value inside a string including an option to compare by the case (default is to ignore case)
e.g.
"XYZ".ContainsValue("x");
or
"XYZ".ContainsValue("x", StringComparison.Ordinal);
HasValue - Looks for a specified value inside a string including an option to compare by the case (default is to ignore case)
e.g.
"XYZ".HasValue("x");
or
"XYZ".HasValue("x", StringComparison.Ordinal);
EqualsIgnoreCase - A wrapper around Equals
to always be a case-insensitive check. The check is also safe and returns false if the string is null or empty.
e.g.
"XYZ".EqualsIgnoreCase("xyz");
string value = null;
value.EqualsIgnoreCase(null);
both examples return true
SubstringOrEmpty - A safe version of substring that returns an empty string if the substring is completely out of range or the value found if the length is out of range
e.g.
"Hello World".SubstringOrEmpty(12, 10);
returns ""
"Hello World".SubstringOrEmpty(9, 10);
returns "ld"
SubstringAfterValue - A substring that returns the remaining string after a given string or character ignoring the case by default
e.g.
"Hello World".SubstringAfterValue("Hello ");
returns "World"
"Hello World".SubstringAfterValue('W');
returns "orld"
Case Sensitive substring
"Hello world World".SubstringAfterValue('W', StringComparison.Ordinal);
returns "orld"
"Hello world World".SubstringAfterValue("world", StringComparison.Ordinal);
returns " World"
SubstringAfterLastValue - A substring that returns the remaining string after the last occurrence of a given character or string ignoring case by default
e.g.
"Hello World hello@world.com".SubstringAfterLastValue("Hello");
returns "@world.com"
"Hello World hello@world.com".SubstringAfterLastValue('W');
returns "orld.com"
Case Sensitive substring
"Hello world World".SubstringAfterLastValue('W', StringComparison.Ordinal);
returns "orld"
"Hello world World".SubstringAfterLastValue("world", StringComparison.Ordinal);
returns " World"
SubstringBeforeValue - A substring that returns the string before a given string or character ignoring the case by default
e.g.
"Hello World".SubstringBeforeValue(" World");
returns "Hello"
"Hello World".SubstringBeforeValue('W');
returns "Hello "
Case Sensitive substring
"Hello world World".SubstringBeforeValue(" World", StringComparison.Ordinal);
returns "Hello world"
"Hello world World".SubstringBeforeValue('W', StringComparison.Ordinal);
returns "Hello world "
SubstringBeforeLastValue - A substring that returns the string before the last occurrence of a given string or character ignoring the case by default
e.g.
"Hello World World".SubstringBeforeLastValue(" world");
returns "Hello World"
"Hello World World".SubstringBeforeLastValue('w');
returns ""Hello World "
Case Sensitive substring
"Hello World world".SubstringBeforeLastValue(" World", StringComparison.Ordinal);
returns "Hello"
"Hello World world".SubstringBeforeLastValue('W', StringComparison.Ordinal);
returns ""Hello "
SafeTrim - A safe version of trim that does not throw an exception if the string is null
e.g.
(null as string).SafeTrim();
returns null
SafeStartsWith - A safe version of starts with that does not throw an exception if the string is null
e.g.
(null as string).SafeStartsWith("Find");
returns false
SafeEndsWith - A safe version of ends with that does not throw an exception if the string is null
e.g.
(null as string).SafeEndsWith("Find");
returns false
IsBase64 - A check to see if a string has been base64 encoded e.g.
var value = Convert.ToBase64String(Encoding.UTF8.GetBytes("some value"), Base64FormattingOptions.None);
value.IsBase64();
returns true
or
"some value".IsBase64();
returns false
IsAllNumbers - Determine if the whole string value contains only number characters
e.g.
"1235456".IsAllNumbers();
returns true
e.g.
"1235456ABc".IsAllNumbers();
returns false
IsAllAlpha - Determine if the whole string value contains only alpha characters
e.g.
"ABCDE".IsAllAlpha();
returns true
e.g.
"ABCDE123$".IsAllAlpha();
returns false
IsAllAlphaOrNumbers - Determine if the whole string value contains only alpha or number characters
e.g.
"1A235456c".IsAllAlphaOrNumbers();
returns true
e.g.
"1A235456c%%".IsAllAlphaOrNumbers();
returns false
ValueOrDefault - Get the value from a dictionary or return the default for the value type (the default value can be specified if needed)
e.g.
new Dictionary<string, int> { { "some text", 1 }, { "more text", 2 } }.ValueOrDefault("value", 99);
returns 99
new Dictionary<string, int> { { "some text", 1 }, { "more text", 2 } }.ValueOrDefault("some text");
returns 1
TryGetValueOrDefault - Same as ValueOrDefault only uses the dictionary method TryGetValue which depending on dictionary size can be more performant
e.g.
new Dictionary<string, int> { { "some text", 1 }, { "more text", 2 } }.TryGetValueOrDefault("value", 99);
returns 99
new Dictionary<string, int> { { "some text", 1 }, { "more text", 2 } }.TryGetValueOrDefault("some text");
returns 1
Partition - Splits an IEnumerable collection into multiple collections based on size
e.g.
var items = new List<int> { 0, 1, 2, 3, 4, 5, 6 };`
var result = items.Partition(4);
returns an IEnumerable<IEnumerable<int>>
that contains 2 lists one with 0, 1, 2, 3 one with 4, 5, 6
Page - Get a part or page of data from an IEnumerable
e.g.
var items = new List<int> { 0, 1, 2, 3, 4, 5, 6 };`
items.Page(0, 2);
returns a list with 0 and 1 in
IsNullOrEmpty - Performs a check on a collection for null or empty
e.g.
List<int> list = null;
if(list.IsNullOrEmpty())
{
// Perform logic here
}
or
var list = new List<string> { "somevalue" };
if(!list.IsNullOrEmpty())
{
// Perform logic here
}
IsValueInList - Checks for a value that exists in the list
e.g.
var list = new[] { "One", "Two", "Three", "Four" };
if(list.IsValueInList("five"))
{
// Perform logic here
}
or
var list = new[] { 1, 2, 3, 4 };
if(list.IsValueInList(3))
{
// Perform logic here
}
Join - A fluent version of the classic string.Join()
methods
If you have an array/list of strings and you want to output them as a comma-delimited string, then the code you would have to write would look like this:
var items = new string[] { "Me", "You", "Them", "Us" };
var itemsAsString = string.Join(", ", items);
The output would be:
Me, You, Them, Us
Wouldn't it be nice to use a fluent style, like this:
var items = new string[] { "Me", "You", "Them", "Us" };
var itemsAsString = items.Join(", ");
Or this:
var items = new List<string> { "Me", "You", "Them", "Us" };
var itemsAsString = items.Join(", ");
Well, now you can.
The output of both of those examples would be:
Me, You, Them, Us
The same as if you used the original.
It is possible to use this extension on other types as there is a generic extension that will accept an IEnumerable<T>
. However, the class must have an overridden ToString()
method, otherwise, it will return the class name.
As an example, this test class doesn't override the ToString()
method, so when we call it like this:
var items = new List<TestClass> { "Me", "You", "Them", "Us" };
var itemsAsString = items.Join(", ");
The output is like this:
Useful.Extensions.Tests.TestClasses.TestClass
Not very useful, is it? So, ensure the class in your list has an override for the ToString()
method before using this extension.
Combine - Combine multiple lists together
e.g.
var list = new List<string> { "Hello" };
var additionalList = new List<string> { "World" };
var anotherList = new List<string> { "Bye" };
list.Combine(additionalList, anotherList);
Results in a list containing "Hello", "World", "Bye"
Add Many - Add multiple items to a list
e.g.
var items = new List<string> { "Hello", "World" };
items.AddMany("Another", "Day");
Results in a list containing "Hello", "World", "Another", "Day"
ToStringOrEmpty - String representation of a nullable value or an empty string if the nullable has no value
IsEqual - Checks if the nullable value is equal to another value
e.g.
int? value = null;
value.IsEqual(14);
returns false
IsNullOrDefault - Check if the value is null or the default value
e.g.
int? value = 0;
value.IsNullOrDefault();
returns true as the default for an int is 0
bool? value = true;
value.IsNullOrDefault();
returns false
ValueOrDefault - Retrieves the value of the nullable or the default of the type if there is not a value or specified default value
e.g.
long? value = 123456;
value.ValueOrDefault();
returns 123456
int? value = null;
value.ValueOrDefault(-1);
returns -1 as that was the default specified
[Flags]
public enum TestEnum : short
{
None = 0,
Item1 = 1,
Item2 = 2,
Item3 = 4,
Item4 = 8,
Item5 = 16,
Item6 = 32
}
Contains - Returns a boolean to denote if the specified flag has been set
var testValue = TestEnum.Item1;
var result = testValue.Contains(TestEnum.Item1);
result would be true
HasAnyOf - Returns a boolean to denote if any of the specified flags have been set
var testValue = TestEnum.Item1 | TestEnum.Item5;
var result = testValue.HasAnyOf(TestEnum.Item5);
result would be true
HasAllOf - Returns a boolean to denote if all of the specified flags have been set
var testValue = TestEnum.Item1 | TestEnum.Item5 | TestEnum.Item2;
var result = testValue.HasAllOf(TestEnum.Item5, TestEnum.Item2);
result would be true
Set - Set flags on a given enum
var value = TestEnum.None;
var result = value.Set(TestEnum.Item2, TestEnum.Item6);
result.All(TestEnum.Item2, TestEnum.Item6)
result would be true
UnSet - UnSet flags on a give enum
var value = TestEnum.None;
var result = value.UnSet(TestEnum.Item2);
result.Contains(TestEnum.Item2);
return would be false
public enum TestEnumDescription
{
[Description("none")]
None = 0,
[Description("one")]
Item1 = 1,
[Description("two")]
Item2 = 2
}
GetDescription - Gets the description value from an enum
var item = TestEnumNoDescription.Item1;
var description = item.GetDescription();
return value would be "one"
ShouldBeWithinRangeOf - Determines that a DateTime is close to an expected date. Has an optional parameter of an int. This represents the variation we can accept in seconds.
e.g.
var dateToCheck = new DateTime(2017, 01, 01, 12, 30, 00);
var expectedDateToCheck = new DateTime(2017, 01, 01, 12, 30, 05);
dateToCheck.ShouldBeWithinRangeOf(expectedDateToCheck);
returns true, as the date and time we are checking are within five seconds of the expected date and time.
var dateToCheck = new DateTime(2017, 01, 01, 12, 30, 00);
var expectedDateToCheck = new DateTime(2017, 01, 01, 12, 30, 11);
dateToCheck.ShouldBeWithinRangeOf(expectedDateToCheck);
returns false, as the date and time we are checking are over the default ten seconds of the expected date and time.
var dateToCheck = new DateTime(2017, 01, 01, 12, 30, 00);
var expectedDateToCheck = new DateTime(2017, 01, 01, 12, 30, 19);
dateToCheck.ShouldBeWithinRangeOf(expectedDateToCheck, 20);
returns true, as the date and time we are checking are within nineteen seconds of the expected date and time. This is using the optional parameter where we can set the seconds of variation.
Between - Determines if the DateTime is between a specified start and end time. Has an optional parameter of a bool. This defaults to false and doesn't include the specific start time or end time.
e.g.
var dateToCheck = new DateTime(2017, 01, 01, 12, 30, 00);
var startOfRange = new DateTime(2016, 12, 15, 12, 30, 00);
var endOfRange = new DateTime(2017, 01, 02, 23, 59, 59);
dateToCheck.Between(startOfRange, endOfRange);
returns true, as the date and time being checked are within the ranges supplied.
var dateToCheck = new DateTime(2017, 01, 02, 12, 30, 00);
var startOfRange = new DateTime(2016, 12, 15, 12, 30, 00);
var endOfRange = new DateTime(2017, 01, 02, 12, 30, 00);
dateToCheck.Between(startOfRange, endOfRange, true);
returns true, as the date and time being checked are within the ranges supplied, and as the inclusive parameter is true, we are going right up to the end range limit.
GetAge - Get the age from a given date representing the Date of Birth and now or a given date
e.g.
var dob = new DateTime(2003, 07, 04);
var ageToday = dob.GetAge();
ageToday (ran on 05/01/2020) would return 16. If supplying the date then it would return the same using the syntax below
e.g.
var dob = new DateTime(2003, 07, 04);
var ageToday = dob.GetAge(new DateTime(2020, 01, 05));
Allow date/time now entries to be testable. This was inspired by Oren Eini https://ayende.com/blog/3408/dealing-with-time-in-tests.
e.g.
var now = SystemTime.Now();
var utc = SystemTime.UtcNow();
To set up a time in the unit tests so that when System.Now() or System.UtcNow() is called it will be a specified date/time
SystemTime.Now = () => new DateTime(2000, 1, 1, 10, 10, 47);
SystemTime.UtcNow = () => new DateTime(2000, 1, 1, 9, 10, 47);
Clone - An implementation of a deep clone using serialization
The next set are helper functions rather than extensions
GetValueFromAnonymousType - Get values from dynamic
objects by property name
e.g.
var anon = new { Text = "Hello" };`
ObjectExtensions.GetValueFromAnonymousType<string>(anon, "Text");
returns "Hello"
GetValueFromAnonymousTypeOrDefault - As with GetValueFromAnonymousType but returns a default value for the type if not found e.g.
var anon = new { Text = "Hello" };`
ObjectExtensions.GetValueFromAnonymousTypeOrDefault<string>(anon, "Value");
returns null
IsPropertyInAnonymousType - Checks to see if there is a property of a given name inside a dynamic
object
e.g.
var anon = new { Text = "Hello" };`
ObjectExtensions.IsPropertyInAnonymousType(anon, "Value");
returns false