public RateLimitBucket GetBucket(RestRequestMethod method, string route, object route_params, out string url) { var rparams_props = route_params.GetType() .GetTypeInfo() .DeclaredProperties; var rparams = new Dictionary <string, string>(); foreach (var xp in rparams_props) { var val = xp.GetValue(route_params); if (val is string xs) { rparams[xp.Name] = xs; } else if (val is DateTime dt) { rparams[xp.Name] = dt.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); } else if (val is DateTimeOffset dto) { rparams[xp.Name] = dto.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture); } else if (val is IFormattable xf) { rparams[xp.Name] = xf.ToString(null, CultureInfo.InvariantCulture); } else { rparams[xp.Name] = val.ToString(); } } var guild_id = rparams.ContainsKey("guild_id") ? rparams["guild_id"] : ""; var channel_id = rparams.ContainsKey("channel_id") ? rparams["channel_id"] : ""; var webhook_id = rparams.ContainsKey("webhook_id") ? rparams["webhook_id"] : ""; var id = RateLimitBucket.GenerateId(method, route, guild_id, channel_id, webhook_id); // using the GetOrAdd version with the factory has no advantages as it will allocate the delegate, closure object and bucket (if needed) instead of just the bucket RateLimitBucket bucket = Buckets.GetOrAdd(id, new RateLimitBucket(method, route, guild_id, channel_id, webhook_id)); url = RouteArgumentRegex.Replace(route, xm => rparams[xm.Groups[1].Value]); return(bucket); }