How do I specify a constraint that says "collection of nullable types"?

1.3k Views Asked by At

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?

4

There are 4 best solutions below

0
On BEST ANSWER

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.

0
On

Just don't use Contains. Iterate through the collection and compare the values to null explicitly:

internal static T1 ThrowIfNullEmptyOrContainsNull<T1, T2>(T1 value, string name)
    where T1 : ICollection<T2>
{
    if (ReferenceEquals(value, null))
        throw new ArgumentNullException(name);

    if (value.Count == 0)
        throw new ArgumentException("Empty collection not allowed", name);

    foreach (var item in value)
        if (item == null)
            throw new ArgumentException("Collection contains one or more null elements", name);

    return value;
}
2
On

What about something like:

if (value.Any(item => item == null))
{
    throw new ArgumentException("Collection contains one or more null elements", name);
}

As in:

internal static T1 ThrowIfNullEmptyOrContainsNull<T1, T2>(T1 value, string name)
    where T1 : ICollection<T2>
{
    if (ReferenceEquals(value, null)) 
        throw new ArgumentNullException(name);

    if (value.Count == 0) 
        throw new ArgumentException("Empty collection not allowed", name);

    if (value.Any(item => item == null)) 
        throw new ArgumentException("Collection contains 1 or more null items", name);

    return value;
}
2
On

What about an extension method. Wrap with exceptions or messages as required

  public static bool IsNullOrEmpty<T>(this ICollection<T> alist) where T:class
    {
        if (alist == null || alist.Count == 0){
            return true;
        }
        if (alist.Any(t => t == null)) {
            return true;
        }
        return false;
    }

use:

 if ( myList.IsNullOrEmpty  ) {
   //.. exception, error handling
  }

No need to pass the type of MyList since MyList must implement ICollection Might be useful for you no need to pass type requirement.
Added T:Class to suggestion. But you knew about that already :-)