To recap from last time, we want to create a simple library that allows us to express validation in a straightforward way, allowing us to concentrate on the rules we’re checking, not the boilerplate needed to make it work.

We’ll start by declaring an abstract class, ValidationResult to represent the results of validation:

/// <summary>
/// Represents one or more messages that result from a validation check
/// </summary>
public abstract class ValidationResult
{
    private protected ValidationResult()
    {
    }
}

Why an abstract class instead of an interface? Because there are things you can do with an abstract class that you can’t do with an interface. More on that in a later post in this series.

The private protected constructor is there because we’re not creating a general purpose ancestor class; we’re going to create a small number of specialised descendant classes, each for a specific purpose. With this visibility (new in C# 7.2), we can restrict visibility to descendents in the same assembly only. This helps us to ensure that only our planned descendants are present.

In earlier versions of C#, there are two ways to achieve similar results. We could declare the constructor as internal, limiting visibility to the assembly, or we could make it private and nest all of the descendents within ValidationResult.

Our first two descendents are to capture two basic results of validation - success and error.

public sealed class SuccessResult : ValidationResult
{
    public SuccessResult()
        : base()
    {
    }
}

public sealed class ErrorResult : ValidationResult
{
    public string Message { get; }

    public ErrorResult(string message)
    {
        Message = message 
            ?? throw new ArgumentNullException(nameof(message));
    }
}

The ErrorResult contains a single error message, the SuccessResult is even simpler than that.

To make it easier to create these - and to create a more declarative style of API - let’s create a static class that acts as a factory:

public static class Validation
{
    private static readonly ValidationResult _success
        = new SuccessResult();

    public static ValidationResult Success()
        => _success;

    public static ValidationResult Error(string message)
        => new ErrorResult(message);

    public static ValidationResult ErrorWhen(
        bool isError, string message)
        => isError ? new ErrorResult(message) : _success;
}

Notice that we create a static reference instance of SuccessResult that we reuse whenever needed. Success results are boring - they don’t have any unique information, so we don’t need to use a different one each time - and reuse helps to keep the memory footprint down.

If you look back at the declaration for ValidationResult at the top of the file, you’ll see that it represents one or more messages that result from a validation check - but our current implementations only capture a single result.

To handle the aggregation of multiple results, we’ll now create AggregateResult:

public sealed class AggregateResult : ValidationResult
{
    private readonly ImmutableHashSet<ValidationResult> _results;

    internal AggregateResult(ImmutableHashSet<ValidationResult> results)
    {
        _results = results;
    }
}

To finish off this post, here are two puzzles for you to consider.

The constructor for AggregateResult is internal and takes a ImmutableHashSet<ValidationResult> parameter. Clearly, I’m not intending that consumers do the aggregation themselves. How do you think our consumers will end up with instances of AggregateResult if they’re not calling that constructor? The use of ImmutableHashSet<T> is a clue.

All of the factory methods on Validation return a ValidationResult - and so do all of the sample methods that I presented in the earlier post. We don’t want our consumers to have to test each instance to work out the type - that’s a long way from the level of convenience we’d like to achieve. How would you modify these classes to make it easy to react to the outcome of validation?

Prior post in this series:
Why we need better validation
Next post in this series:
Recovery of validation types

Comments

blog comments powered by Disqus