I'm trying to build a validation attribute that will validate the size of a collection,
I want the method to be able to handle reference types like string and also non reference types like int. I tried to use IEnumerable<object>
but that stays null in case I pass a IEnumerable<int>
.
This is my current code -
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
public class EnumerableSizeAttribute : ValidationAttribute<IEnumerable<object>>
{
protected override ValidationResult? IsValid(IEnumerable<object>? enumerable, ValidationContext validationContext)
{
//check collection size
}
}
What should I use as parameter to allow me to pass any collection type into the method? Thanks for the help!
CodePudding user response:
Generic variance rules between value and reference types are... complex.
If you need to avoid boxing, then consider making the method itself generic and take IEnumerable<T>
, so that the caller specifies it (usually implicitly, so no code change).
If that isn't an option: consider using the non-generic IEnumerable
APIs. Or IList
or ICollection
since you're after the count, which you can then access cheaply via .Count
without paying any of the boxing / iteration overheads associated with IEnumerable
.
CodePudding user response:
In C# 11/.NET 7 and later you can have generic attributes :
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
public class EnumerableSizeAttribute<T> : ValidationAttribute<IEnumerable<T>>
{
protected override ValidationResult? IsValid(IEnumerable<T>? enumerable, ValidationContext validationContext)
{
//check collection size
}
}
Inside IsValid
you can check whether enumerable
is an ICollection<T>
, thus using the cheap Count
property to check the size :
if(enumerable is ICollection<T> col && col.Count <...)
{
}
You may be able to use property pattern matching too, although I'm still not comfortable with the syntax. Something like
if(enumerable is ICollection<T> {Count: < MaxLength} col)
{
}