Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attribute-based Guards? #74

Closed
antmdvs opened this issue Jul 21, 2020 · 2 comments
Closed

Attribute-based Guards? #74

antmdvs opened this issue Jul 21, 2020 · 2 comments

Comments

@antmdvs
Copy link

antmdvs commented Jul 21, 2020

Have you considered an approach using attributes (maybe DataAnnotations) in the method's parameter lists, analogous to this capability in Java EE? I searched issues and PRs for a related discussion but came up empty.

@ardalis
Copy link
Owner

ardalis commented Jul 21, 2020

Those have to be evaluated somehow externally, I think, much like model validation works in ASP.NET MVC. Guard Clauses as they're used in this project are standalone, self-contained static functions that don't require any kind of wrapper or other plumbing around how they're used or how functions using them are called. If I'm mistaken and there's a way to implement an attribute-based approach that wouldn't force dependencies on how the methods using them were called, I'm open to it.

@antmdvs
Copy link
Author

antmdvs commented Jul 21, 2020

This wouldn't be until .NET 5/C#9, but there's an upcoming compiler feature called C# Source Generators. I was hoping we could have a generator that inspects the parameter attributes and emits the guard code (this occurs at compile-time). However, since Source Generators cannot modify existing code by design, FWICT we'd still need a call within the ctor to invoke the gen'd code. The design I came up involves partial methods for the guard code:

    public partial class MyClass
    {
        // Partial method declaration (the definition will be generated by the source generator).
        // This decl itself could be generated by a Code Fix. The Code Fix would have to ensure to name this method
        // uniquely so as to disambiguate it from other methods in the class that may have the same signature.
        static partial void Guard(int fooParam);

        int _foo;

        public MyClass([Positive] int fooParam)
        {
            Guard(fooParam);  // calls the generated partial method

            _foo = fooParam;
        }
    }
    // MyClass.GuardClauses.cs

    // THIS IS GENERATED CODE. DO NOT MODIFY
    public partial class MyClass
    {
        static partial void Guard(int fooParam)
        {
            Guard.Against.NegativeOrZero(fooParam, "fooParam"); // no need for nameof() as this is gen'd code
        }
    }

Yeah, unfortunately, I think there's too much ceremony because of the fact that source generators can't modify existing source to stick the guards at the beginning of the constructor. Unless there's a better strategy, this necessitates partial classes/methods (that need to avoid signature ambiguity no less). Even if we had an analyzer to detect missing partial methods and their invocations and provide a Code Fix to generate/update them, that adds even more ceremony:

  1. Write constructor (or other method)
  2. Decorate parameters with attributes
  3. Apply Code Fix to generate/update partial GuardXyz method declaration and invocation.

which provides little benefit over the existing approach IMO. The Code Fix in (3) might as well just generate inlined Guards instead of all the partial madness. That seems interesting at first, too, but then you have 2 sources of truth and need a mechanism to keep them in synch so 👎

If Source Generators allowed modifying existing code, I think it would be a different story. Leaving this here for now in case someone sees something I missed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants