Why is this an issue?
Methods with a long parameter list are difficult to use because maintainers must figure out the role of each parameter and keep track of their position.
void SetCoordinates(int x1, int y1, int z1, int x2, int y2, int z2) // Noncompliant
{
// ...
}
The solution can be to:
- Split the method into smaller ones
// Each function does a part of what the original setCoordinates function was doing, so confusion risks are lower
void SetOrigin(int x, int y, int z)
{
// ...
}
void SetSize(int width, int height, int depth)
{
//
}
- Find a better data structure for the parameters that group data in a way that makes sense for the specific application domain
// In geometry, Point is a logical structure to group data
readonly record struct Point(int X, int Y, int Z);
void SetCoordinates(Point p1, Point p2)
{
// ...
}
This rule raises an issue when a method has more parameters than the provided threshold.
Exceptions
The rule does not count the parameters intended for a base class constructor.
With a maximum number of 4 parameters:
public class BaseClass
{
public BaseClass(int param1)
{
// ...
}
}
public class DerivedClass : BaseClass
{
public DerivedClass(int param1, int param2, int param3, string param4, long param5) : base(param1) // Compliant by exception
{
// ...
}
}
The rule does not count a parameter that a framework, a dependency injection container, or a runtime binding supplies, because it is not part of the argument list a caller writes. Such a parameter is annotated with one of the following attributes, or with an attribute deriving from one of them:
- ASP.NET Core:
Microsoft.AspNetCore.Mvc.FromServicesAttribute,Microsoft.AspNetCore.Mvc.FromStateAttribute - Dependency injection:
Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribute - Azure Functions input bindings:
Microsoft.Azure.WebJobs.BlobAttribute,Microsoft.Azure.WebJobs.CosmosDBAttribute - Orleans:
Orleans.Runtime.PersistentStateAttribute - Hot Chocolate:
HotChocolate.ParentAttribute,HotChocolate.ServiceAttribute,HotChocolate.SchemaServiceAttribute,HotChocolate.ScopedServiceAttribute,HotChocolate.EventMessageAttribute,HotChocolate.GlobalStateAttribute,HotChocolate.ScopedStateAttribute,HotChocolate.LocalStateAttribute
The remaining parameters are still counted, so an issue is raised when they alone exceed the threshold. With a maximum number of 4 parameters:
public class OrderController : ControllerBase
{
public IActionResult Create([FromServices] IOrderService service, int param1, int param2, int param3, long param4) // Compliant by exception: 'service' is not counted
{
// ...
}
public IActionResult Update([FromServices] IOrderService service, int param1, int param2, int param3, long param4, string param5) // Noncompliant: 5 parameters are supplied by the caller
{
// ...
}
}
The rule does not raise on the constructor of a type that a dependency injection container or a framework instantiates, because a caller never writes its argument list.
This is the case for a constructor marked with Microsoft.Extensions.DependencyInjection.ActivatorUtilitiesConstructorAttribute. Only
the marked constructor is exempt, and the attribute is not inherited by derived types.
Still with a maximum number of 4 parameters:
public class Service
{
[ActivatorUtilitiesConstructor]
public Service(int param1, int param2, int param3, string param4, long param5) // Compliant by exception
{
// ...
}
}
It is also the case for every constructor of a type annotated with Microsoft.AspNetCore.Mvc.ApiControllerAttribute or
Microsoft.AspNetCore.Mvc.ControllerAttribute, and of a type deriving from or implementing one of the following:
- ASP.NET Core:
Microsoft.AspNetCore.Mvc.ControllerBase,Microsoft.AspNetCore.Mvc.Controller,Microsoft.AspNetCore.Mvc.ViewComponent,Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata,Microsoft.AspNetCore.Mvc.RazorPages.PageModel,Microsoft.AspNetCore.Razor.TagHelpers.TagHelper,Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper,Microsoft.AspNetCore.Http.IMiddleware,Microsoft.AspNetCore.Authorization.AuthorizationHandler<TRequirement>,Microsoft.AspNetCore.Authorization.IAuthorizationHandler - SignalR:
Microsoft.AspNetCore.SignalR.Hub,Microsoft.AspNetCore.SignalR.Hub<T>,Microsoft.AspNet.SignalR.Hub - Hosting:
Microsoft.Extensions.Hosting.BackgroundService,Microsoft.Extensions.Hosting.IHostedService - ASP.NET:
System.Web.Mvc.Controller,System.Web.Http.ApiController - MediatR:
MediatR.IRequestHandler<TRequest>,MediatR.IRequestHandler<TRequest, TResponse>,MediatR.INotificationHandler<TNotification>,MediatR.IPipelineBehavior<TRequest, TResponse> - MassTransit:
MassTransit.IConsumer
Still with a maximum number of 4 parameters:
public class HomeController : Controller
{
public HomeController(int param1, int param2, int param3, string param4, long param5) // Compliant by exception
{
// ...
}
}
Two cases fall outside this exception:
- Attribute types, because their arguments are written at every usage site. MVC filter attributes such as
Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttributeimplementIFilterMetadata, yet the rule still raises on their constructors unless the constructor is marked withActivatorUtilitiesConstructorAttribute. - Types annotated with
Microsoft.AspNetCore.Mvc.NonControllerAttribute, which excludes a type and its derived types from MVC controller discovery.