5

I'm creating an attribute which accepts up to 4 arguments.

I've coded this way:

internal class BaseAnnotations
{

    public const string GET = "GET";
    public const string POST = "POST";
    public const string PATCH = "PATCH";
    public const string DELETE = "DELETE";


    public class OnlyAttribute : Attribute
    {
        public bool _GET = false;
        public bool _POST = false;
        public bool _PATCH = false;
        public bool _DELETE = false;

        public OnlyAttribute(string arg1)
        {
            SetMethod(arg1);
        }

        public OnlyAttribute(string arg1, string arg2)
        {
            SetMethod(arg1);
            SetMethod(arg2);
        }

        public OnlyAttribute(string arg1, string arg2, string arg3)
        {
            SetMethod(arg1);
            SetMethod(arg2);
            SetMethod(arg3);
        }

        public OnlyAttribute(string arg1, string arg2, string arg3, string arg4)
        {
            SetMethod(arg1);
            SetMethod(arg2);
            SetMethod(arg3);
            SetMethod(arg4);
        }

        public void SetMethod(string arg)
        {
            switch (arg)
            {
                case GET: _GET = true; break;
                case POST: _POST = true; break;
                case PATCH: _PATCH = true; break;
                case DELETE: _DELETE = true; break;
            }
        }
    }
}

I need to use it like this:

public class ExampleModel : BaseAnnotations
{
    /// <summary>
    /// Example's Identification 
    /// </summary>
    [Only(GET, DELETE)]
    public long? IdExample { get; set; }

    // ...

Is there any way to code in only one constructor the 4 constructors above to avoid repetition?

I'm thinking in something like JavaScript's spread operator (...args) => args.forEach(arg => setMethod(arg)).

Thanks in advance.

  • 2
    have you looked into the `params` syntax ? Would it suit your needs ? – Mathieu VIALES Aug 09 '18 at 15:01
  • 2
    Check out "optional parameters". – Bradley Uffner Aug 09 '18 at 15:01
  • 1
    Possible duplicate of [Function with variable number of arguments](https://stackoverflow.com/questions/9784630/function-with-variable-number-of-arguments) – dymanoid Aug 09 '18 at 15:02
  • Use `List` as argument – Chetan Aug 09 '18 at 15:02
  • You could make every parameter nullable, and only set the fields to parameters that are not null. Seeing as you are using only string parameters, you could just check if they are null and if so, don't set the field. – Ryan Wilson Aug 09 '18 at 15:02
  • I would consider rewriting this to use a [`Flags` enumeration](https://stackoverflow.com/questions/8447/what-does-the-flags-enum-attribute-mean-in-c), I think it would be easier to use. – Bradley Uffner Aug 09 '18 at 15:07
  • @BradleyUffner no, GET, DELETE is more natural for attributes than GET | DELETE – vadzim dvorak Aug 09 '18 at 15:16
  • @VadzimDvorak I'd disagree - I'm very comfortable and familiar seeing `|`-style in attributes; see, for example, `AttributeUsageAttribute` or `MethodImplAttribute` – Marc Gravell Aug 09 '18 at 15:17
  • Thank you all for your ideas. When I'm able to upvote, I will upvote all your answers hahah – Mônica Santos Aug 09 '18 at 15:28
  • 1
    You could use the default constructor and make them bool properties that default to false. So you'd end up with [Only(Get=true, Delete=true)]. – Hans Passant Aug 09 '18 at 15:31

4 Answers4

11

I'm going to suggest rethinking your design here. Consider:

[Flags]
public enum AllowedVerbs
{
    None = 0,
    Get = 1,
    Post = 2,
    Patch = 4,
    Delete = 8
}
public class OnlyAttribute : Attribute
{
    private readonly AllowedVerbs _verbs;
    public bool Get => (_verbs & AllowedVerbs.Get) != 0;
    public bool Post => (_verbs & AllowedVerbs.Post) != 0;
    public bool Patch => (_verbs & AllowedVerbs.Patch) != 0;
    public bool Delete => (_verbs & AllowedVerbs.Delete ) != 0;
    public OnlyAttribute(AllowedVerbs verbs) => _verbs = verbs;
}

Then callers can use:

[Only(AllowedVerbs.Get)]

or

[Only(AllowedVerbs.Post | AllowedVerbs.Delete)]
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 2
    @KennethK. it is *always* recommended to include an explicit zero in a flags enum, and highly recommended to call it `None`; it'll make things a lot more obvious when you see zeros :) – Marc Gravell Aug 09 '18 at 15:09
  • But that's not my question. – Kenneth K. Aug 09 '18 at 15:10
  • @KennethK. then what is? – Marc Gravell Aug 09 '18 at 15:10
  • I think we can chalk this up to a difference in opinion on style, but to confirm: there's no technical reason why you cannot use `AllowedVerbs.None` instead of `0`, yes? – Kenneth K. Aug 09 '18 at 15:12
  • @KennethK. are you talking about on the right of the `!=` ? subjective convention, really; the compiler won't care; for me, though, there are two interesting bit tests - "any" (`!= 0` or `!= TheEnum.None`), or "all" (`== TheEnum.Whatever`). The "any" test is marginally cheaper, and to me, an "any" test isn't really testing for "is this `None`" - it is testing explicitly for zero/non-zero, so to me (subjective), `!= 0` is clearer – Marc Gravell Aug 09 '18 at 15:15
  • Thaaanks for the bit manipulation idea. [Here is](https://pastebin.com/Thuu2ZGL) how I ended up coding. – Mônica Santos Aug 09 '18 at 15:27
  • 1
    @MônicaSantos fair enough; I prefer `[Flags]` enums, but: your code, your choice :) – Marc Gravell Aug 09 '18 at 15:33
3

Good answers, though consider going with 4 attributes instead. For your example, this might work.

public class GetAttribute: Attribute {}
public class PostAttribute: Attribute {}
public class PatchAttribute: Attribute {}
public class DeleteAttribute: Attribute {}

[GET] [DELETE]
public long? IdExample { get; set; }

It's simple and direct. Sure there are more attributes, but you're likely to have many more instances where you need them.

Each attribute would have a default constructor. The mere existence of the attribute for each operation would be enough to convey what is allowed.

Kit
  • 20,354
  • 4
  • 60
  • 103
1

You can try this

public OnlyAttribute(params string[] parameters)
{
    foreach(var p in parameters) SetMethod(p);
}
tym32167
  • 4,741
  • 2
  • 28
  • 32
  • @MarcGravell but it close to `I'm thinking in something like JavaScript's spread operator (...args) => args.forEach(arg => setMethod(arg))` – tym32167 Aug 09 '18 at 15:06
1
 public OnlyAttribute(params string[] parameters)
 {
        if (parameters.Length > 4) throw new ArugumentException(nameof(parameters));

        foreach (var param in parameters)
        {
            SetMethod(param);
        }
 }
d.moncada
  • 16,900
  • 5
  • 53
  • 82