C#: Enumerator-type solution without explicit casts

Whether you're a newbie or an experienced programmer, any questions, help, or just talk of any language will be welcomed here.

Moderator: Coders of Rage

Post Reply
User avatar
MarauderIIC
Respected Programmer
Respected Programmer
Posts: 3406
Joined: Sat Jul 10, 2004 3:05 pm
Location: Maryland, USA

C#: Enumerator-type solution without explicit casts

Post by MarauderIIC »

If you've used enumerators in C#, you've probably found out that there is absolutely no way for it to implicitly cast to integer, even if you declare that the enum is integer-only (by going enum myEnumName : int { } ). This is solution is higher-maintenance than a regular enum, but allows one to enable implicit casting. This is my interpretation of an enigmatic reply to http://stackoverflow.com/questions/5779 ... -return-it

ConstrainedNumber.cs

Code: Select all

using System;

namespace Editor
{
    /// <summary>
    /// Based on Triynko's reply to http://stackoverflow.com/questions/577946/can-i-avoid-casting-an-enum-value-when-i-try-to-use-or-return-it
    /// </summary>
    /// <typeparam name="T">Type</typeparam>
    public class ConstrainedNumber<T>
    {
        /// <summary>
        /// The actual number
        /// </summary>
        public readonly T data;

        /// <summary>
        /// Constraint for this constrained number
        /// </summary>
        /// <typeparam name="U">Type (should probably be the same as T)</typeparam>
        /// <param name="constrainer">Constraint definition</param>
        /// <returns>True if constraint is met, false otherwise</returns>
        protected delegate bool NumberConstraint<U>(U constrainer);

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="value"></param>
        /// <param name="constraint"></param>
        protected ConstrainedNumber(T value, NumberConstraint<T> constraint)
        {
            if (!constraint(value))
                throw new ArgumentException("Value does not meet constraint.");
            data = value;
        }

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="value"></param>
        protected ConstrainedNumber(T value)
        {
            data = value;
        }

        /// <summary>
        /// Convert from ConstrainedNumber to its T-type
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        public static implicit operator T(ConstrainedNumber<T> i)
        {
            return i.data;
        }

        /// <summary>
        /// Define equality
        /// </summary>
        /// <param name="obj">Object to compare to</param>
        /// <returns>True on data equal, false otherwise</returns>
        public override bool Equals(object obj)
        {
            ConstrainedNumber<T> test = obj as ConstrainedNumber<T>;
            if (test != null)
            {
                if (this.data.Equals(test.data))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Required to define Equals()
        /// </summary>
        /// <returns>No idea</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
}
Enumerators.cs

Code: Select all

    /// <summary>
    /// Using a class instead of an enumerator to limit range and avoid casts everywhere.
    /// </summary>
    public class SheetTypes : ConstrainedNumber<int>
    {
        private static readonly NumberConstraint<int> constraint = (int x) => (x >= 0) && (x <= 2);

        public static readonly SheetTypes TILE = new SheetTypes(0);
        public static readonly SheetTypes OBJECT = new SheetTypes(1);
        public static readonly SheetTypes NUM_SHEET_TYPES = new SheetTypes(2);

        private SheetTypes(int value) : base(value, constraint) { }
        private SheetTypes(SheetTypes original) : base(original) { }
        public static implicit operator SheetTypes(int value)
        {
            switch (value)
            {
                case 0: return TILE;
                case 1: return OBJECT;
                case 2: return NUM_SHEET_TYPES;
            }
            throw new ArgumentException("Value " + value.ToString() + " out of range.");
        }
    }
If you use this, it removes compile-time const optimization (as readonly values can be modified in constructors, but cannot have a const reference type -- and all classes are reference types). This also means you cannot compare these in switch statements, and must use ifs. Structs are non-reference types, but cannot inherit, apparently. So if speed is a large issue, it's probably worse than casting everywhere.
I realized the moment I fell into the fissure that the book would not be destroyed as I had planned.
Apoc
Chaos Rift Newbie
Chaos Rift Newbie
Posts: 7
Joined: Fri Jul 31, 2009 5:52 am

Re: C#: Enumerator-type solution without explicit casts

Post by Apoc »

I'm going to assume you're using .NET 2.0? If so, that's a very sneaky way to do things. However, I don't really see the payoff. You end up going from decently-clean switch statements, to ugly-as-hell if/else if/else statements.

If not, just create an extension method for the enum in question to avoid the issues all together.

Eg;

Code: Select all

    public enum LogicResponse
    {
        Running = 0,
        Failed = 1,
        Success = 2
    }

    public static class LogicResponseExtensions
    {
        public static int ToInt32(this LogicResponse resp)
        {
            return (int) resp;
        }
    }
Then you can do, for instance,

Code: Select all

int response = LogicResponse.Running.ToInt32();
Also; when comparing enum members, just use logical operators on them.

Code: Select all

if (returnedLogicResponse > LogicResponse.Running) { Foo(); }
P.S; it's 'enumeration', not 'enumerator'. Enumerator is what you get from stuff such as IEnumerable/IEnumerable<T>. They allow you to 'enumerate' over a collection. (Obvious exceptions.) Enumeration is the actual 'enum' keyword (or Enum type if you're so inclined)

Enumerator = Enumerates over a collection. (AkA; iteration)
Enumeration = constant value collection. Just like C/C++/etc/etc

P.P.S; Yes, I'm anal about C# code. :)
User avatar
MarauderIIC
Respected Programmer
Respected Programmer
Posts: 3406
Joined: Sat Jul 10, 2004 3:05 pm
Location: Maryland, USA

Re: C#: Enumerator-type solution without explicit casts

Post by MarauderIIC »

Apoc wrote:I'm going to assume you're using .NET 2.0? If so, that's a very sneaky way to do things. However, I don't really see the payoff. You end up going from decently-clean switch statements, to ugly-as-hell if/else if/else statements.
It's been a while since I've looked how I'm using this code, or for that matter at C# code in general, but what's preventing me from using a switch, since the cast is implicit? And I can use it as an index into lists without explicit casts. That was the payoff -- I was using it as an index into a list a lot. C++ habits, I suppose. Again, though, it's not really my code =)
I realized the moment I fell into the fissure that the book would not be destroyed as I had planned.
Post Reply