Skip to content
Jessica Hill By Jessica Hill Software Engineer I
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>

AddRange()

Dictionary Extensions πŸ““

AddIfNotExists()

Adds a value to a key, if the key does not already exist.

AddIfNotExists()

ReplaceIfExists()

Replaces a value in a key, but only if the key already exists.

ReplaceIfExists

Merge()

The union of two dictionaries. Note that this uses AddIfNotExists() semantics, so the values in the first dictionary will be preserved.

Merge

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.

DistinctPreserveOrder()

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.

DistinctBy()

Concatenate()

This gives you the ability to concatenate multiple enumerables, using the params pattern.

Concatenate()

HasMinimumCount()

This determines whether the enumerable has at least a given number of items in it.

HasMinimumCount

AllAndAtLeastOne()

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.

AllAndAtLeastOne

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.

GetMemberExpression

ExtractPropertyName() This extracts a property name from a lambda expression, throwing if that expression is not a MemberExpression

ExtractPropertyName

List Extensions πŸ““

RemoveAll()

This removes all items from a list that match a predicate.

RemoveAll

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.

AsBase64

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).

Base64UrlEncode()

Base64UrlDecode()

Convert the provided string from a base 64 representation of its byte representation in the UTF8 encoding with a URL-safe representation.

Base64UrlDecode()

AsStream()

The Introduction to Rx.NET 2nd Edition (2024) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

Provide a stream over the string in the specified encoding.

AsStream()

EscapeContentType()

Escape a content type string.

EscapeContentType()

FromBase64()

Decode a string from a base64-encoded byte array with the specified text encoding.

FromBase64()

GetGraphemeClusters()

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.

GetGraphemeClusters()

Reverse()

Reverse the string.

Reverse()

UnescapeContentType()

Unescape a content type string.

UnescapeContentType()

ToCamelCase()

Convert a string to camel case from pascal case.

ToCamelCase()

Task Extensions πŸ““

  • Casts Task/Task<?> to Task<T> result type with or without a cast of the actual result value

CastWithConversion()

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.

ForEachAsync

ForEachAtIndex()

Execute an action for each item in the enumerable with the index of the item in the enumerable.

ForEachAtIndex()

ForEachAtIndexAsync()

Execute an async action for each item in the enumerable, in turn, with the index of the item in the enumerable.

ForEachAtIndexAsync()

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.

ForEachFailEnd()

ForEachFailEndAsync()

Execute an async action for each item in the enumerable.

Programming C# 12 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

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.

ForEachFailEndAsync()

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.

ForEachUntilFalse()

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.

ForEachUntilFalseAsync()

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.

ForEachUntilTrue()

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.

ForEachUntilTrueAsync()

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>>.

WhenAllMany()

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.

Jessica Hill

Software Engineer I

Jessica Hill

Jessica comes from a Biosciences background, having gained a 1st Class Bachelor of Science in Biology from The University of Manchester.

During the lockdown Jessica used the opportunity to explore her interest in technology by taking two Code First Girls online courses; Introduction to Web Development and Python Programming.

This led Jessica to look for job opportunities in the technology sector and joined endjin's 2021 apprenticeship cohort, which had over 200 applicants.