RecoreFX fills the most common needs for C# code after the .NET standard library.
Install from NuGet:
dotnet add package RecoreFX
If you're like me, there's a bunch of useful methods that you write for every project you work on. Some are simple, such as IDictionary.GetOrAdd()
. Others are more subtle, such as SecureCompare.TimeInvariantEquals()
.
There's a lot of low-hanging fruit. Want JavaScript-style IIFEs? Write Func.Invoke()
. Want ad-hoc RAII like in Go? Create a Defer
type. Tired of checking IsAbsoluteUri
? Define an AbsoluteUri
subtype. (But let's be honest, who really checks?)
All of this starts to add up, though. That's why I put it all together into a single installable, unit-tested package.
There are some other goodies here that are further reaching:
Apply()
is a generic extension method for any type. It gives you a way to call any method with postfix syntax:
public async Task<IEnumerable<DirectoryListing>> GetDirectoryListingAsync(RelativeUri? listingUri)
=> await listingUri
.Apply(uri => uri ?? new RelativeUri("api/v1/listing"))
.Apply(httpClient.GetStreamAsync)
.ApplyAsync(async body => await JsonSerializer.DeserializeAsync<IEnumerable<DirectoryListing>>(body, jsonOptions)
?? Enumerable.Empty<DirectoryListing>());
It can also be used to simplify null-propagation logic
when the ?.
operator can't be used:
// var route = contentEndpoint is null ? null : $"{contentEndpoint}?path={forwardSlashPath}";
var route = contentEndpoint?.Apply(x => $"{x}?path={forwardSlashPath}");
Defer
is analogous to Golang's defer
statement. It lets you do some kind of cleanup before exiting a method.
The classic way to do this in C# is with try-finally
:
try
{
Console.WriteLine("Doing stuff");
}
finally
{
Console.WriteLine("Running cleanup");
}
With Defer
and C# 8's new using
declarations, we can do it more simply:
using var cleanup = new Defer(() => Console.WriteLine("Running cleanup"));
Console.WriteLine("Doing stuff");
Unit
is a type with only one value (like how Void
is a type with no values).
Imagine a type ApiResult<T>
that wraps the deserialized JSON response from a REST API.
Without Unit
, you'd have to define a separate, non-generic ApiResult
type for when the response doesn't have a body:
ApiResult postResult = await PostPersonAsync(person);
ApiResult<Person> getResult = await GetPersonAsync(id);
With Unit
, you can just reuse the same type:
ApiResult<Unit> postResult = await PostPersonAsync(person);
ApiResult<Person> getResult = await GetPersonAsync(id);
In the definition of PostPersonAsync()
, just return Unit.Value
:
ApiResult<Unit> PostPersonAsync(Person person)
{
// ...
return new ApiResult<Unit>(Unit.Value);
}
Either<TLeft, TRight>
gives you a type-safe union type that will be familiar to TypeScript users:
Either<string, int> either = "hello";
var message = either.Switch(
l => $"Value is a string: {l}",
r => $"Value is an int: {r}");
Result<TValue, TError>
gives you a way to handle "expected" errors. You can think of it as a nicer version of the TryParse
pattern:
async Task<Result<Person, HttpStatusCode>> GetPersonAsync(int id)
{
var response = await httpClient.GetAsync($"/api/v1/person/{id}");
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
var person = JsonSerializer.Deserialize<Person>(json);
return Result.Success<Person, HttpStatusCode>(person);
}
else
{
return Result.Failure<Person, HttpStatusCode>(response.StatusCode);
}
}
It also makes it easy to build up an error context as you go along rather than terminating immediately:
Result<IBlob, IBlob>[] results = await Task.WhenAll(blobsToWrite.Select(blob =>
Result.TryAsync(async () =>
{
await WriteBlobAsync(blob);
return blob;
})
.CatchAsync((Exception e) =>
{
Console.Error.WriteLine(e);
return Task.FromResult(blob);
})));
List<IBlob> successes = results.Successes().ToList();
List<IBlob> failures = results.Failures().ToList();
Of<T>
makes it easy to define "type aliases."
Consider a method with the signature:
void AddRecord(string address, string firstName, string lastName)
It's easy to make mistakes like this:
AddRecord("Jane", "Doe", "1 Microsoft Way"); // oops!
You can prevent this with strong typing:
class Address : Of<string> {}
void AddRecord(Address address, string firstName, string lastName) {}
AddRecord("Jane", "Doe", "1 Microsoft Way"); // compiler error
While defining a new type that behaves the same way string
does usually takes a lot of boilerplate, Of<T>
handles this automatically:
var address = new Address { Value = "1 Microsoft Way" };
Console.WriteLine(address); // prints "1 Microsoft Way"
var address2 = new Address { Value = "1 Microsoft Way" };
Console.WriteLine(address == address2); // prints "true"
Optional<T>
gives you compiler-checked null safety if you don't have nullable references enabled (or if you're on .NET Framework):
Optional<string> opt = "hello";
Optional<string> empty = Optional<string>.Empty;
opt.Switch(
x => Console.WriteLine("Message: " + x),
() => Console.WriteLine("No message"));
Optional<int> messageLength = opt.OnValue(x => x.Length);
string message = opt.ValueOr(default);
These are all borrowed from functional programming, but the goal here isn't to turn C# into F#. RecoreFX is meant to encourage more expressive, type-safe code that's still idiomatic C#.
AbsoluteUri
andRelativeUri
AsyncAction
,AsyncAction<T>
, etc.AsyncFunc<TResult>
,AsyncFunc<T, TResult>
, etc.Defer
Either<TLeft, TRight>
,Optional<T>
, andResult<TValue, TError>
Func
ObjectExtensions
Of<T>
Unit
AnonymousEqualityComparer<T>
MappedComparer<T, TMapped>
MappedEqualityComparer<T, TMapped>
- Extension methods:
IEnumerable<T>.Argmax()
IEnumerable<T>.Argmin()
IEnumerable<T>.Enumerate()
IEnumerable<T>.Flatten()
IEnumerable<T>.ForEach()
IEnumerable<T>.NonNull()
IEnumerable<T>.Product()
IEnumerable<T>.ToAsyncEnumerable()
IEnumerable<T>.ToLinkedList()
IEnumerable<T>.Zip()
IEnumerable<KeyValuePair<TKey, TValue>>.OnKeys()
IEnumerable<KeyValuePair<TKey, TValue>>.OnValues()
IEnumerable<KeyValuePair<TKey, TValue>>.ToDictionary()
Have a look at the contributor's guide.
If it's generally useful (as opposed to oriented towards a specific application) and fills a common need in C# programming, then there's no reason why not! Feel free to open an issue or submit a PR for discussion.
The design principles of RecoreFX are:
- Generally useful
- Common sense-ness
- Follows the programming paradigm of standard C#
If you like RecoreFX, check out these other libraries:
RecoreFX v1 targets .NET Standard 2.0, so it works with .NET Framework ≥ 4.6.1.
The RecoreFX Sample App is a fully worked out Web app with a console app client using RecoreFX.