dotnet icon indicating copy to clipboard operation
dotnet copied to clipboard

Make Guard extensible

Open AndreasHeisel opened this issue 2 years ago • 2 comments

Overview

Although the Guard class is a good tool to validate parameters in an uniform way, it is limited to the built-in checks and cannot be extended with application or library specific checks.

Being able to do so would improve code quality. Parameter validation could be performt using a single syntax/pattern. Library authors could deliver checks that help their users to use the library better,

API breakdown


/*
 * Extend Guard with a static property named 'That' of type Guard`.
 * This will be the new anchor to provide and perform checks.
 *
 * The same approach used in the 'Assert' class of 'Microsoft.VisualStudio.TestTools.UnitTesting' to make it extensible.
 */

public sealed partial class Guard
{
    // Prevent instances other then 'That'.
    private Guard() {}

    // Extensibility point
    public static Guard That { get; } = new();
}

/*
 * Rebuild the existing checks as extension methods on 'Guard'.
 *
 * The existing checks could be marked as 'Obsolete'.
 *
 * New checks can easily be added as extension methods, inside and outside the package.
 */

public static class GuardExtensions
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void IsNull<T>(this Guard guard, T? value, [CallerArgumentExpression("value")] string name = "")
    {
        if (value is null)
        {
            return;
        }

        ThrowHelper.ThrowArgumentExceptionForIsNull(value, name);
    }
}

Usage example


class MyClass
{
    public void MyMethod(object param1, string param2)
    {
        Guard.That.IsNotNull(param1);
        Guard.That.MyCheck(param2);
    }
}

Breaking change?

No

Alternatives

Alternative 1

Making the existing checks instance methods would allow the same syntax, but would break binary compatibility.

Alternative 2

Distibuting Guard as source code package would make it extensible as Guard is a partial class. But it would be difficult to expose new checks in libraries. If more than one package reexposes Guard you will get ambiguity trouble.

Additional context

No response

Help us help you

Yes, I'd like to be assigned to work on this item

AndreasHeisel avatar Aug 19 '22 12:08 AndreasHeisel

I would also like this feature. Ardalis already has a popular project, but having it in this package makes sense.

I have a preference for using his same extensibility point as I find it clearer and more readable, Guard.Against.

The current way seems backwards to me. But if the team wants to stick with the current direction, then I would recommend Guard.Ensures.

MisinformedDNA avatar Aug 30 '22 03:08 MisinformedDNA

@Sergio0694 Any thoughts on this?

MisinformedDNA avatar Sep 21 '22 01:09 MisinformedDNA

+1 for Against, with Ensure (no final s) as a fallback preference.

rdeago avatar Nov 07 '22 12:11 rdeago

This is not something we're planning on adding. Furthermore, it would also likely make the codegen worse. Extensibility is something that should eventually be solved once C# gets support for shapes/extensions.

Sergio0694 avatar Dec 18 '22 02:12 Sergio0694

The shapes/extensions is a proposal, it's not planned work. But why introduce a half-baked feature instead of just adding a dependency to an existing library like Ardalis.Guard?

MisinformedDNA avatar Dec 18 '22 19:12 MisinformedDNA

It's not a half-baked feature. The fact it's not extensible doesn't make it so, that just wasn't one of the design goals. Furthermore, as I've mentioned, switching to instance methods would be a massive breaking change and also likely cause codegen regressions, which we do not want. These APIs are specifically designed to make the codegen as compact as possible.

"instead of just adding a dependency to an existing library like Ardalis.Guard?"

Taking a dependency on a 3rd party library would be a complete non-starter for any package in the Toolkit.

Sergio0694 avatar Dec 18 '22 19:12 Sergio0694

But it should have been a design goal. Why would developers want to use a feature that only works in the scenarios that the author chooses?

Also, we wouldn't need to switch to instance methods, we would be adding an extensibility point and methods to call the existing static methods, but now developers can extend it.

As for codegen, I don't understand the issue. Are you referring to source code generators or IL?

MisinformedDNA avatar Dec 19 '22 23:12 MisinformedDNA