16

I just figured out that System.Enum is not easy to implement as a generic type. The compiler throws an error when comparing two enums of type T:

if(button.Identifier == Identifier) // (in AbstractInputDevice)

I believe I cannot compare these two Enums because they are not actually known to be Enums. And thus no comparison method is available. How do I compare them for equality?

Here are more details:

public class Button<TEnum> where TEnum : struct, IConvertible, IComparable, IFormattable {
   public TEnum Identifier {
        get;
        private set; //Set in the ctor
    }
}

and

public abstract class AbstractInputDevice<TEnum> where TEnum : struct, IConvertible, IComparable, IFormattable {

   private List<Button<TEnum>> _buttons = new List<Button<TEnum>>();

   public Button<TEnum> GetButton(TEnum Identifier){
        foreach(Button<TEnum> button in _buttons){
            if(button.Identifier == Identifier) //<- compiler throws
                return button;
        }
        Debug.Log("'" + GetType().Name + "' cannot return an <b>unregistered</b> '" + typeof(Button<TEnum>).Name + "' that listens to '" + typeof(TEnum).Name + "." + Identifier.ToString() + "'.");
        return null;
    }
}

An InputDevice might look like that:

public class Keyboard : AbstractInputDevice<KeyCode> {
    private void Useless(){
        Button<KeyCode> = GetButton(KeyCode.A);
    }
}

I used this resource:
Create Generic method constraining T to an Enum

Noel Widmer
  • 4,444
  • 9
  • 45
  • 69
  • 2
    What is the exact error the compiler is giving you? – Ron Beyer Apr 28 '15 at 20:42
  • 1
    You may want to try `button.Identifier.Equals(Identifier)`, and to make things more concise: `var button = _buttons.Where(b => b.Identifier.Equals(Identifier)).FirstOrDefault();`. – Alex Apr 28 '15 at 20:44
  • @RonBeyer I use Unity3D and their compiler is sometimes a little confusing. Right now it says "unexpected symbol '=='" as if there would be a typo. But before some changes it said something different like "'==' cannot be applied to operands of type 'TEnum and 'TEnum'" – Noel Widmer Apr 28 '15 at 20:45
  • I'd say you need to make an [MCVE](http://stackoverflow.com/help/mcve) – Blorgbeard Apr 28 '15 at 20:46
  • @Alex Are you serius? No error anymore! – Noel Widmer Apr 28 '15 at 20:46

2 Answers2

24

Instead of the impossible

button.Identifier == Identifier

you should use

EqualityComparer<TEnum>.Default.Equals(button.Identifier, Identifier)

This avoids boxing the value into an object box (or IComparable box).

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • That would be a better compare, agreed – Alex Apr 28 '15 at 20:58
  • Alright, since Alex agreed to this solution (I already marked his as answer) I will mark this one instead. – Noel Widmer Apr 28 '15 at 21:01
  • @NoelWidmer You can still use Linq `_buttons.FirstOrDefault(b => EqualityComparer.Default.Equals(b.Identifier, Identifier))` much like Alex suggests, but it is more a matter of style and personal preference. – Jeppe Stig Nielsen Apr 28 '15 at 21:07
  • 1
    Interesting that `EqualityComparer(of enumType).Default` avoids boxing, given that enum types don't implement `IEquatable(themselves)`. I wonder what `EqualityComparer(of T).Default` looks for besides `IEquatable`? – supercat May 26 '15 at 14:58
  • @supercat Maybe I was wrong, and maybe it ends up calling `Enum.Equals(object)`, with boxing, after all? Have you investigated it? Also, does `EqualityComparer(of Nullable(of SomeStruct)).Default.Equals` lead to boxing of the nullables? – Jeppe Stig Nielsen May 26 '15 at 15:05
  • @JeppeStigNielsen: It doesn't box, at least in my current .NET, which makes me curious if there are other kinds of value types for which `EqualityComparer.Default` includes optimization, and whether any such behaviors have changed since 2.0; it would certainly be possible to write a "make default equality comparer" method which would analyze the things being compared and generate code to do the appropriate comparisons, but I have no idea if .NET does so. – supercat May 26 '15 at 15:40
  • 4
    @supercat Simply looking at the run-time type of the `.Default` instance gives some clue. For an enum type `E` I get ``System.Collections.Generic.EnumEqualityComparer`1[E]``, so there exists special support for enums. Given that, surely they took care and avoided boxing. For a struct `G` which does implement ``IEquatable`1[G]``, I get ``System.Collections.Generic.GenericEqualityComparer`1[G]``. Finally, for a struct `N` with no generic equality support, I get ``System.Collections.Generic.ObjectEqualityComparer`1[N]``. ***Addition:*** With .NET 4.5.2. – Jeppe Stig Nielsen May 27 '15 at 11:58
  • @JeppeStigNielsen: I wonder how hard it would have been to add a `ValueTypeBitwiseEqualityComparer` for structures that are bitwise-compared. By my understanding, the default `ValueType.Equals` goes through the trouble of identifying whether a struct should be bitwise-compared and does a bitwise comparison if so, rather than calling `Equals` on all the members, but the benefit of doing that is somewhat muffled by the amount of work that gets repeated with every comparison. I would think that for structs whose members are all bitwise-comparable performance could be... – supercat May 27 '15 at 14:40
  • ...improved significantly by such an optimization, though for best results it may be good to define an attribute for structs which implement `IEquatable` but could be safely bitwise compared, so that when included in other structs the outer structs could also be bitwise compared. – supercat May 27 '15 at 14:41
0

You are trying to perform a reference comparison on a value type (struct), use Equals for equality instead:

public Button<TEnum> GetButton(TEnum Identifier) {
    var button = _buttons
        .Where(b => EqualityComparer<TEnum>.Default.Equals(b.Identifier, Identifier))
        .FirstOrDefault();

    if (button == null)
        Debug.Log("'" + GetType().Name + "' cannot return an <b>unregistered</b> '" + typeof(Button<TEnum>).Name + "' that listens to '" + typeof(TEnum).Name + "." + Identifier.ToString() + "'.");
    return button;
}

The button.Identifier == Identifier statement cannot be performed, because the == operator does not exist for structs. On a class it would have performed a reference comparison.

And as @JeppeStigNielsen noted in his answer, to prevent a boxing equality comparison, it is better to use the EqualityComparer<TEnum>.Default.Equals method.

Community
  • 1
  • 1
Alex
  • 13,024
  • 33
  • 62