/RecoreFX

A simple library for a better C#

Primary LanguageC#MIT LicenseMIT

RecoreFX

GitHub Actions Badge Azure Pipelines Badge NuGet Badge

RecoreFX fills the most common needs for C# code after the .NET standard library.

Installation

Install from NuGet:

dotnet add package RecoreFX

Why use it?

Convenience methods

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.

New stuff

There are some other goodies here that are further reaching:

Apply()

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

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

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>

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>

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>

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>

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

What's in it?

Recore

Recore.Collections.Generic

Recore.Linq

Recore.Security.Cryptography

Recore.Threading.Tasks

Contributing

Have a look at the contributor's guide.

FAQs

Why doesn't this have $TYPE or $METHOD?

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.

How does this compare to $LIBRARY?

The design principles of RecoreFX are:

  1. Generally useful
  2. Common sense-ness
  3. Follows the programming paradigm of standard C#

If you like RecoreFX, check out these other libraries:

Does this work with .NET Framework?

RecoreFX v1 targets .NET Standard 2.0, so it works with .NET Framework ≥ 4.6.1.

Sample App

The RecoreFX Sample App is a fully worked out Web app with a console app client using RecoreFX.

Reference

https://recorefx.github.io