An Overview of the Corvus.Extensions Library
The Corvus GitHub Organisation contains a series of open-source repositories that provide tools and support for common requirements such as tenancy storage, authentication and more. Corvus encapsulate endjin's recommended practices for building .NET applications. These practices are founded in the knowledge gained through over a decade of real world experience building .NET applications for our customers.
Each blog post in this series will focus on a single Corvus repository, outlining the repository's intended use and how to get started.
Overview of Corvus.Extensions
The first Corvus repository under the spotlight is Corvus.Extensions. Corvus.Extensions provides a library of useful extensions to .NET types. These extension methods are utility/helper methods that we've found useful across many solutions. Over time, some of these methods have become so useful that they have become part of the .NET Framework as the C# language has evolved.
There are various types of extension methods in the Corvus.Extensions repository, including:
- Collection extensions
- Dictionary extensions
- Enumerable extensions
- Exception message extensions
- List extensions
- String extensions
- Task extensions
- Traversal extensions
Corvus.Extensions is built for netstandard2.0.
How to get started
Corvus.Extensions is available on NuGet. To get started with Corvus.Extensions, add a reference to the Corvus.Extensions NuGet package in your project, by running the following command:
dotnet add package Corvus.Extensions
How to use
Within the Corvus.Extensions repository, there are a series of Polyglot Notebooks (under the folder Corvus.Extensions.Documentation.Examples) that provide examples on how to use the various extension methods.
The README file provides links to each of the notebooks along with animations of the code snippets in action.
Casting
The CastTo<>
method used to useful to avoid boxing in certain generic conversion cases. However, the CLR can now determine when boxing is unnecessary, therefore CastTo<>
is no longer useful and we're not including examples for it.
This is commonly used in generic scenarios.
CastTo
supports a wide range of conversions with good efficiency trade-offs, including scenarios where (T)(object)value
will fail.
Collection Extensions π
An AddRange()
extension for ICollection<T>
Dictionary Extensions π
AddIfNotExists()
Adds a value to a key, if the key does not already exist.
ReplaceIfExists()
Replaces a value in a key, but only if the key already exists.
Merge()
The union of two dictionaries. Note that this uses AddIfNotExists()
semantics, so the values in the first dictionary will be preserved.
Enumerable Extensions π
DistinctPreserveOrder()
This emits an enumerable of the distinct items in the target, preserving their original ordering.
The built-in LINQ operator Distinct
can be used to return the distinct elements from a sequence. However, the documentation makes no guarantee about preserving the original ordering of the elements.
DistinctBy()
This is now built into .NET (since .NET 6.0). The extension is only of use for .NET Core 3.1 and .NET Framework.
This allows you to provide a function to provide the value for equality comparison for each item.
Concatenate()
This gives you the ability to concatenate multiple enumerables, using the params pattern.
HasMinimumCount()
This determines whether the enumerable has at least a given number of items in it.
This is an efficient implementation of the combination of the built-in LINQ operators Any()
& All()
that avoids starting the enumeration twice. It determines if the collection is non-empty, and that every element also matches some predicate.
Lambda Expression Extensions π
GetMemberExpression()
This extracts a MemberExpression
from a LambdaExpression
, throwing if the body is not a MemberExpression
.
This allows a more direct expression of the expectation that an expression has this particular form. It allows us to avoid cluttering up the code with exception throwing, which can improve readability.
ExtractPropertyName()
This extracts a property name from a lambda expression, throwing if that expression is not a MemberExpression
List Extensions π
RemoveAll()
This removes all items from a list that match a predicate.
String Extensions π
- Get as a stream in various encodings
- Base 64 encode/decode (with or without URL safety)
- Reverse
- To camel case
AsBase64()
Interpret the input string as base64, and then use the specified encoding (UTF-8 for the overload where you don't specify it) to decode the resulting bytes.
Base64UrlEncode()
Convert the provided string to a base 64 representation of its byte representation in the UTF8 encoding, with a URL-safe representation (an encoding also known as base64url).
Base64UrlDecode()
Convert the provided string from a base 64 representation of its byte representation in the UTF8 encoding with a URL-safe representation.
AsStream()
Provide a stream over the string in the specified encoding.
EscapeContentType()
Escape a content type string.
FromBase64()
Decode a string from a base64-encoded byte array with the specified text encoding.
Enumerate the grapheme clusters in a string.
This method is a wrapper around StringInfo.GetTextElementEnumerator, which returns an enumerator that iterates through the text elements of a string. GetGraphemeClusters()
returns an IEnumerable<string>
, meaning the functionality can be used with LINQ.
Reverse()
Reverse the string.
UnescapeContentType()
Unescape a content type string.
ToCamelCase()
Convert a string to camel case from pascal case.
Task Extensions π
- Casts
Task
/Task<?>
toTask<T>
result type with or without a cast of the actual result value
CastWithConversion()
Traversal Extensions π
Various ForEach
extensions, including:
- async methods
- aggregating and delaying exceptions until the end of the traversal
- with indexing
- until predicates are true/false
ForEachAsync()
Execute an async action for each item in the enumerable.
ForEachAtIndex()
Execute an action for each item in the enumerable with the index of the item in the enumerable.
ForEachAtIndexAsync()
Execute an async action for each item in the enumerable, in turn, with the index of the item in the enumerable.
ForEachFailEnd()
Even if an exception gets thrown for one or more of the items, this perseveres, ensuring that it attempts to invoke the action for every single item. If there are any exceptions, it only throws them after every single action has completed/thrown; it wraps exceptions in an AggregateException
to enable it to report multiple exceptions if there were more than one.
ForEachFailEndAsync()
Execute an async action for each item in the enumerable.
Returns a task which completes when the enumeration has completed.
If any operation fails, then the enumeration is continued to the end when an Aggregate Exception is thrown containing the exceptions thrown by any failed operations.
ForEachUntilFalse()
Execute an action for each item in the enumerable. It will stop early if one of the callbacks says so. It returns false if it decided to stop before reaching the end of the enumeration.
ForEachUntilFalseAsync()
Execute an async action for each item in the enumerable. It will stop early if one of the callbacks says so. It returns false if it decided to stop before reaching the end of the enumeration.
ForEachUntilTrue()
Execute an action for each item in the enumerable. It will stop early if one of the callbacks says so. It returns true if it decided to stop before reaching the end of the enumeration.
ForEachUntilTrueAsync()
Execute an async action for each item in the enumerable.
Returns a task whose result is True if the action terminated early, otherwise returns False.
TaskEx π
A class that provides a single static method: TaskEx.WhenAllMany()
TaskEx.WhenAllMany()
Passes the elements of a sequence to a callback that projects each element to a Task<IEnumerable<T>>
and flattens the sequences produced by the resulting tasks into one Task<IList<T>>
.
As you can see, Corvus.Extensions is a variety pack of tools that we've found to be useful over 10 years of .NET development. If you have any thoughts on better ways to approach particular problems, or related tools you'd like to see, then we'd like to hear them β please comment below, or in the Issues section of the project.
This post was a collaboration between Jessica Hill, Eli Gascon, Liam Mooney and Charlotte Gayton.