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: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 hadbool obviouslyFalse = false;
.With generics the same applies in that with:
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:
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:Because
default(TEl) == null
is always true for nullable types (includingNullable<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.