C
C#9mo ago
zobweyt

Attributes confusion

hey! I'm having trouble choosing an approach when using attributes. I'm building a wrapper around System.Threading.RateLimiting. I have an attribute RateLimitAttribute which is responsible for the basic logic and creation of PartitionedRateLimiter. here’s the problem: I want to be able to create custom ignore policies (to have no limit in custom conditions) and pass them directly to the RateLimitAttribute, but I don’t know how to do this correctly and what approach to choose. initially I wanted to make a base class RateLimitPolicyAttribute for all policies and later use it like this:
[RateLimit]
[IgnoreOwner, IgnoreAdmins, IgnorePremiumUsers]
public static void Foo() => throw NotImplementedException();
[RateLimit]
[IgnoreOwner, IgnoreAdmins, IgnorePremiumUsers]
public static void Foo() => throw NotImplementedException();
but the policies here can exist without the main RateLimitAttribute also, I thought about passing the policies directly to the main attribute, but this approach is not possible because the values in attribute parameters must be constant, so I can't just pass a list of policies. additionally, it'll be a very long line in length if I have many policies how can I implement this most conveniently?
12 Replies
PixxelKick
PixxelKick9mo ago
Option 1: If these are user defined (not constants), then they have to pass in a list of Types most likely Option 2: If these are specific constants that you define and are an immutable list, I would just use a flagged Enum
zobweyt
zobweytOP9mo ago
these are user defined (not constants) as for the list of types, how do you type them correctly? for example, just Type[] policies? but how do you get the needed method to execute without hard-coding its name in a string? I was trying to find something like "generic type" so I could use it like that: Type<BaseType>[] policies or should I just do it like that:
if (type is not BaseType baseType)
throw Exception();

baseType.MyMethod();
if (type is not BaseType baseType)
throw Exception();

baseType.MyMethod();
PixxelKick
PixxelKick9mo ago
You have to use reflection, it's why you'll notice many attributes take in Types as their params for basically this exact problem space
zobweyt
zobweytOP9mo ago
thank you!! I'll look into it :)
canton7
canton79mo ago
Attributes can be generic these days. So you can do [RateLimit<SomeCustomPolicy>], with associated generic type constraints
zobweyt
zobweytOP9mo ago
I thought about this too, but what if you need multiple policies? also, you won't be able to use the [RateLimit] without the generic type
canton7
canton79mo ago
Yeah, you can apply it multiple times, or have multiple versions which take different numbers of types, but it gets a bit messy You can have both [RateLimit] and [RateLimit<Foo>] easily though
zobweyt
zobweytOP9mo ago
if I allow to use multiple [RateLimit] attributes, then there will be multiple PartitionedRateLimiter instances, so the logic might be duplicated however, maybe I still need to rework the core attribute logic to make this possible if there's no policy, then the rate limit should be applied to every request (which is the default policy) maybe I can do a DefaultPolicy and use it like that [RateLimit<DefaultPolicy>] well, it seems to be more customizable speaking globally, what would you prefer as a user of it? pass policies into one attribute as the argument, thereby making the string infinitely long and unreadable, or make several such attributes that accept the policy as a generic argument creating a long chain of attributes on several lines?
canton7
canton79mo ago
Personally I don't like using attributes for that sort of stuff, as it's awkward to customise them. If I need to instantiate my policy with a particular parameter, say, that gets awkward. Particularly if that parameter is only known at runtime
zobweyt
zobweytOP9mo ago
also, all of these attributes will always be executed before processing the request until there is a negative result or all attributes check the conditions (this is because of the framework's nature for which I am trying to implement it). because of this, there may be conflicts: for example, I have this method:
[RateLimit<AdminPolicy>]
[RateLimit<DefaultPolicy>]
public static void Foo() => throw NotImplementedException();
[RateLimit<AdminPolicy>]
[RateLimit<DefaultPolicy>]
public static void Foo() => throw NotImplementedException();
although AdminPolicy skips all requests, the following attribute is always executed after it, which will be valid for all requests
canton7
canton79mo ago
I mean, everything you're saying here isn't set in stone. It's up to you to define how the attributes interact
zobweyt
zobweytOP9mo ago
yeah, I'm of the same opinion, but the framework which I use decides the attributes interaction for me anyway, thank you so much for the suggestion! I'll try to implement something similar and compare it with other ideas :)
Want results from more Discord servers?
Add your server