Question

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?

Was it helpful?

Solution

Generic constraints work on generic methods as well:

bool IsInList<T>(T value, params T[] args) where T : IEquatable<T>

BUT, IEquatable<T> doesn't define operator ==, only Equals(T).

So, you should use Equals() and you don't even need the constraint for that: Equals(object) is member of object.

Also don't forget that Equals won't work if the object is null.

OTHER TIPS

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>.

The `==' operator is generally used for immutable types only - (built-in value types or custom immutable types that have overloaded the == operator. Unless you overload it, if you use it on reference types, (except strings) it only returns true if both variables point to the same instance of the type (same object). It would return false if the two veriables point to different instances of the type even if they both have all the same internal data values.

So You need to restrict the type T to those types that implement the Equals() function, which is intended to determine whether two instances of any type are "equal" and not just that they both point to the same instance... and use that instead. The built-in interface IEquatable<T> expresses this for you. (kinda like IComparable<T> requires that a type has a CompareTo() function)
Also, you can make your function much terser and clearer...

try this:

bool IsInList<T>(T value, params T[] args) where T:IEquatable<T>
{ 
    foreach(var arg in args)          
        if(value.Equals(arg)) return true;
    return false;
 } 

You should also understand the difference between Equals() and == and decide which is more appropriate for whatever your intent is... Check out this reference for more info.

Just replace

arg == value

with

arg.Equals(value)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top