Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Constraints on function

I want to write a generic function that has a constraint on the type. Specifically I want something like this:

bool IsInList<T>(T value, params T[] args)
{
    bool found = false;
    foreach(var arg in args)
    {
        if(arg == value)
        {
            found = true;
            break;
        }
    }
    return found;
 }

The point being that you can check if an item is in a parameter list viz:

if(IsInList("Eggs", "Cheese", "Eggs", "Ham"))

However, the compiler croaks on the equality line. So I want to put in a constraint on the type that it implements IEquatable. However, constraints only seem to work at the class level. Is this correct, or is there some way to specify this generically?

like image 211
JessicaB Avatar asked Sep 25 '10 17:09

JessicaB


1 Answers

Others have mentioned IEquatable<T> which is certainly a good potential constraint.

Another option to remember is EqualityComparer<T>.Default, which will use IEquatable<T> if available, but fall back to object.Equals(object) otherwise. This means you can use it with types which predate generics but override Equals, for example:

bool IsInList<T>(T value, params T[] args)
{
    IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
    bool found = false;
    foreach(var arg in args)
    {
        if(comparer.Equals(arg, value))
        {
            found = true;
            break;
        }
    }
    return found;
}

Note that the default equality comparer also copes with null references, so you don't need to worry about those yourself. If type T hasn't overridden object.Equals(object) or implemented IEquatable<T>, you'll get reference equality semantics (i.e. it will only return true if the exact reference is in the array).

A few other points:

  • Your desire to stick to a single exit point makes the code less readable, IMO. There's no need for an extra variable here:

    bool IsInList<T>(T value, params T[] args)
    {
        IEqualityComparer<T> comparer = EqualityComparer<T>.Default;
        foreach (var arg in args)
        {
            if (comparer.Equals(arg, value))
            {
                return true;
            }
        }
        return false;
    }
    
  • LINQ already contains a method for this, Contains, so you can simplify the code to:

    bool IsInList<T>(T value, params T[] args)
    {
        return args.Contains(value);
    }
    
  • Array effectively contains this functionality too, with IndexOf:

    bool IsInList<T>(T value, params T[] args)
    {
        return Array.IndexOf(args, value) != -1;
    }
    
  • Your method is perhaps a little misleadingly named, given that args is an array, not a List<T>.

like image 91
Jon Skeet Avatar answered Oct 03 '22 22:10

Jon Skeet