j.heidt

Avoid 287at all costs
posts - 14, comments - 11, trackbacks - 1

Friday, April 25, 2008

An Interesting Discovery on Enums

I have been sandboxing with the 3.5 features a bit, when I came across something that has been around since 2.0 that surprised me. I was using an interface that I had created a while ago, IKeyed<K>, a generic interface that lets me declare that an item can be distinguished from other items of its type, or other items of it's key type.

Nothing groundbreaking here:

/// <summary>
/// A class that impliments IKeyed declares that its identity can be assured against other items
/// in its scope that are also keyed.
/// </summary>
/// <typeparam name="KeyType">The type of the key.</typeparam>
public interface IKeyed<KeyType> : IEquatable<KeyType>, IEquatable<IKeyed<KeyType>> where KeyType : IEquatable<KeyType>
{
    /// <summary>
    /// Gets a value indicating whether this instance's key is set.
    /// </summary>
    /// <value>
    ///     <c>true</c> if this instance is key set; otherwise, <c>false</c>.
    /// </value>
    bool IsKeySet { get; }

    /// <summary>
    /// Gets the key that uniquely identifies this class in its scope.
    /// </summary>
    /// <value>The key.</value>
    KeyType Key { get; }
}

Well, so far so good. I have implemented IKeyed on many classes without a problem. Now let's say I want to toss an Enum into the mix, for example:

public enum ProcessStatus
{
    Unknown,
    Uninitialized,
    Initializing,
    Initialized,
    Processing,
    Completed,
    Fault    
}

Already, again, nothing huge here. Just standard issue stuff. Here's where the problem crept in:

I wanted to create a class that was keyed against the ProcessStatus Enumeration

public class ProcessStatusNotifier : IKeyed<ProcessStatus>
{
    ProcessStatus _Status = ProcessStatus.Unknown;

    #region Object Overrides
    public override bool Equals(object obj) {
        if(obj is ProcessStatusNotifier) { return ((ProcessStatusNotifier)obj).Key.Equals(Key); }
        return base.Equals(obj);
    }

    public override int GetHashCode() {
        return Key.GetHashCode();
    }
    #endregion

    #region IKeyed<ProcessStatus> Members
    public bool IsKeySet {
        get { return true; }
    }
    public ProcessStatus Key {
        get { return _Status; }
    }
    #endregion

    #region IEquatable<ProcessStatus> Members
    public bool Equals(ProcessStatus other) {
        return other.Equals(Key);
    }
    #endregion

    #region IEquatable<IKeyed<ProcessStatus>> Members
    public bool Equals(IKeyed<ProcessStatus> other) {
        return other.Equals(Key);
    }
    #endregion
}

A lot of implementations of various interfaces and a few overrides here or there, but nothing too confusing. This code however, doesn't compile. I was surprised, theres no obvious syntax errors I saw.

Here's the error:

Error 1 The type 'JHeidt.Util.Core.ProcessStatus' cannot be used as type parameter 'KeyType' in the generic type or method 'JHeidt.Util.Core.IKeyed<KeyType>'. There is no boxing conversion from 'JHeidt.Util.Core.ProcessStatus' to 'System.IEquatable<JHeidt.Util.Core.ProcessStatus>'. C:\Documents and Settings\Jake\My Documents\Visual Studio 2008\Projects\JHeidt.Util\JHeidt.Util.Core\ProcessStatusNotifier.cs

Hrmm, a generics error, complaining that theres no way the compiler could find to allow the use of KeyType in the class because theres no conversion possible between the enum ProcessStatus and IEquatable<ProcessStatus>. What? Really? I shook my head, that can't be right.

It's right.

Enumerations of type XYZ DO NOT IMPLEMENT IEquatable<XYZ>. I have absolutely no idea why this is, but I have some guesses: due to the way enums are handled, I think that they aren't really first class objects, they are stepchildren of the '.net: everything is objects' world. Enums are all value types, and we know that you can inherit a specific type as the underlying types for an enum (using the :long syntax, etc). I guess that under the hood, enums are simply the value type, perhaps with a System.Type attached to them that says they belong to an enum. I know that we can cast an enum to its underlying value type, and back. And I know that you can create an invalid value by casting to the enum type, so I guess this is the underlying technical reason for the lack of IEquatable<XYZ>, Perhaps the runtime doenst know which IEquatable to use for the comparison (the system numeric types all implement this interface, so maybe it would leave an ambiguous path).

So, I created a (reference type) wrapper for the enum, but it leaves me feeling a bit dirty. Ideally, I would like to create an IEqualityComparer for my enum type, but that rules out using the interface WHERE predicate to specify, unless theres syntax that I'm not familiar with, because now KeyType needs to be either IComparable<KeyType> or have a class specified somewhere accessible that provides a IEqualityComparer for it. Are types that implement IEqualityComparer capable of being cast to IEquatable<KeyType>, I dont think so. There has to be an interfaced based way to do this, but the mental athletics involved already confused me.

posted @ Friday, April 25, 2008 10:26 PM | Feedback (0) |

Powered by: