tags:

views:

110

answers:

6

I am creating a generic class to hold widgets and I am having trouble implementing the contains method:

public class WidgetBox<A,B,C>
{
    public bool ContainsB(B b)
    {
        // Iterating thru a collection of B's
        if( b == iteratorB )  // Compiler error.
        ...
    }
}

Error: Operator '==' cannot be applied to operands of type 'V' and 'V'

If I can not compare types, how am I to implement contains? How do dictionaries, lists, and all of the other generic containers do it??

+3  A: 

Not all objects implement == but all will have Equals (although it may be inherited from Object.Equals).

public class WidgetBox<A,B,C>
{
    public bool ContainsB(B b)
    {
        // Iterating thru a collection of B's
        if( b.Equals(iteratorB) )
        ...
    }
}
Yuriy Faktorovich
A: 
if (b != null)
    if (b.Equals(iteratorB))
        ...
n8wrl
+2  A: 

At compile-time, there is no guarantee that the type in the type argument B provides an equality operator.

Instead, you can do this:

var comparer = EqualityComparer<B>.Default;
foreach (B item in items) {
    if ( comparer.Equals(b, item) ) {
        ....
    }
}
Josh Einstein
+7  A: 

You have a few options here

The first is to use Object.Equals:

if(b.Equals(iteratorB)) {
    // do stuff
}

Be careful using this option; if B does not override Object.Equals then the default comparsion is reference equality when B is a reference type and value equality when B is a value type. This might not be the behavior that you are seeking and is why without additional information I would consider one of the next two options.

The second is to add a constraint that B is IComparable:

public class WidgetBox<A, B, C> where B : IComparable 

so that

if(b.CompareTo(iteratorB) == 0) {
    // do stuff
}

A third is to require an IEqualityComparer<B> be passed to the constructor of WidgetBox

public class WidgetBox<A, B, C> {
    IEqualityComparer<B> _comparer;
    public WidgetBox(IEqualityComparer<B> comparer) {
        _comparer = comparer;
    }
    // details elided
}

Then:

if(_comparer.Equals(b, iteratorB)) {
    // do stuff
}

With this last option you can provide an overload that defaults to EqualityComparer<T>.Default:

public WidgetBox() : this(EqualityComparer<T>.Default) { }
Jason
Depending on the type and whether you only care about equality (vs less than/greater than/equal/etc) it is typically much more efficient to use an IEqualityComparer as opposed to IComparer since equality can often be ruled out very quickly such as with strings of different lengths.
Josh Einstein
+3  A: 

To add to Jason's answer, you can also add where T : IEquatable<T> rather than IComparable. This provides an overload of the Equals method that accepts a T parameter.

public class WidgetBox<A,B,C> where B : IEquatable<B>
{
    public bool ContainsB(B b)
    {
        // Iterating thru a collection of B's
        if( b.Equals(iteratorB) )
        ...
    }
}

This might be preferable to simply using the supplied Object.Equals method, since that checks for the equivalence of two object references (I believe) and so might not provide quite the functionality you want (e.g., if you want two Person objects with the same SSN property to be regarded as equal, or something like that).

But to answer your question of "how do all the .NET collections do it," I believe the Equals method (no constraint) is your answer.

Dan Tao
A: 

If you can get away with it in your case then all it takes is a class constraint on B

public class WidgetBox<A,B,C> where B : class

that will eliminate the problem

Mike Two