I'm trying to create a generic argument-validation method that checks collection parameters for null, empty, or contains a null element.
public void Foo(ICollection<MyType> bar)
{
// Validate parameters
ThrowIfNullEmptyOrContainsNull(bar, "bar");
.
.
.
If I only specify ICollection<T>
in the type constraint, then if (value.Contains(null))
generates an error, since T
may not be a nullable type.
This was what I came up with, but it doesn't seem to be right:
internal static T1 ThrowIfNullEmptyOrContainsNull<T1, T2>(T1 value, string name)
where T1 : ICollection<T2>
where T2 : class
{
if (ReferenceEquals(value, null))
throw new ArgumentNullException(name);
if (value.Count == 0)
throw new ArgumentException("Empty collection not allowed", name);
if (value.Contains(null))
throw new ArgumentException("Collection contains one or more null elements", name);
return value;
}
...but then I have to call the method with explicit argument types, something like this:
public void Foo(ICollection<MyType> bar)
{
// Validate parameters
ThrowIfNullEmptyOrContainsNull<(ICollection<MyType>, MyType>(bar, "bar");
.
.
.
Without explicitly specifying T1 and T2 in the call, I get an error "The type arguments ... cannot be inferred from the usage".
Can anyone shed light on how to do this?
We can compare a non-nullable type with null, because all objects can be cast to object
and hence compared:
bool obviouslyFalse = 1 == null;
This will result in a warning, but is valid. Obviously it's going to always be false
and indeed the compiler will optimise by removing the comparison and giving us the equivalent as if we had bool obviouslyFalse = false;
.
With generics the same applies in that with:
T item = getTFromSomewhere;
bool obviouslyFalseIfTIsntNullable = item == null;
Then this is valid for all possible T
, and while the compiler can't remove the comparison, the jitter can, and indeed will.
Hence therefore we can have:
internal static TCol ThrowIfNullEmptyOrContainsNull<TCol, TEl>(TCol collection, string name)
where TCol : ICollection<TEl>
{
if (ReferenceEquals(value, null))
throw new ArgumentNullException(name);
if (value.Count == 0)
throw new ArgumentException("Empty collection not allowed", name);
foreach(var item in collection)
if(item == null)
throw new ArgumentException("Collection cannot contain null elements", name);
return value;
}
This will work, but is wasteful if we've a large collection of non-nullable types; the jitter is not likely to remove that iteration, even if it does nothing, so it'll still get an enumerator and call MoveNext()
on it until it returns false. We can help it:
internal static TCol ThrowIfNullEmptyOrContainsNull<TCol, TEl>(TCol collection, string name)
where TCol : ICollection<TEl>
{
if (ReferenceEquals(value, null))
throw new ArgumentNullException(name);
if (value.Count == 0)
throw new ArgumentException("Empty collection not allowed", name);
if(default(TEl) == null)
foreach(var item in collection)
if(item == null)
throw new ArgumentException("Collection cannot contain null elements", name);
return value;
}
Because default(TEl) == null
is always true for nullable types (including Nullable<T>
) and always false for non-nullable types, the jitter will optimise by cutting out this comparison for all types, and removing the entire enumeration for non-nullable types. Hence a massive array of integers (for example) will be okayed by the method immediately.