Bindable Enumerations

As much as I love BindableValues, there are still some features that are frustratingly out of reach. First and foremost, I wish we lived in a world where enumerations in C# implemented IEquatable, so they could be made bindable. Despite throwing a couple of workarounds their way, nothing was ever good enough to stick, and my enumerated values continued to loll about, unbound. I couldn’t take it anymore! I settled on a solution that was disappointingly duplicative, but practical: clone the functionality of BindableValues to create a new wrapper specifically for enumerations.

// --------------------------------
// <copyright file="BindableEnum.cs" company="Rumor Games">
//     Copyright (C) Rumor Games, LLC.  All rights reserved.
// </copyright>
// --------------------------------

using System;
using System.Collections.Generic;

/// <summary>
/// BindableEnum class.
/// </summary>
/// <typeparam name="T">Backing value type.</typeparam>
public class BindableEnum<T> where T : struct, IComparable, IFormattable, IConvertible
{
    /// <summary>
    /// Backing value, serialize to show in Inspector.
    /// </summary>
    [UnityEngine.SerializeField]
    private T backingValue;

    /// <summary>
    /// Event raised when bindable value changes.
    /// </summary>
    public Action<T> OnValueChanged;

    /// <summary>
    /// Gets or sets the bindable value.
    /// </summary>
    public T Value
    {
        get
        {
            return this.backingValue;
        }
        set
        {
            if (!this.Equals(value))
            {
                this.backingValue = value;
                if (this.OnValueChanged != null)
                {
                    this.OnValueChanged(this.backingValue);
                }
            }
        }
    }

    /// <summary>
    /// Initializes a new instance of the BindableEnum class.
    /// </summary>
    /// <param name="value">Initial value.</param>
    public BindableEnum(T value)
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("Argument must be an enumerable type.");
        }

        this.backingValue = value;
    }

    /// <summary>
    /// Implicitly convert BindableEnum to backing value type.
    /// </summary>
    /// <param name="bindableEnum">BindableEnum to convert.</param>
    /// <returns>Backing value.</returns>
    public static implicit operator T(BindableEnum<T> bindableEnum)
    {
        return bindableEnum.Value;
    }

    /// <summary>
    /// Determines whether one BindableEnum is equal to another.
    /// </summary>
    /// <param name="a">First BindableEnum with value of type T to compare.</param>
    /// <param name="b">Second BindableEnum with value of type T to compare.</param>
    /// <returns>True if the BindableEnums are equal.</returns>
    public static bool operator ==(BindableEnum<T> a, BindableEnum<T> b)
    {
        if (ReferenceEquals(a, null))
        {
            return ReferenceEquals(b, null);
        }

        return a.Equals(b);
    }

    /// <summary>
    /// Determines whether one BindableEnum type is not equal to another.
    /// </summary>
    /// <param name="x">First BindableEnum with value of type T to compare.</param>
    /// <param name="y">Second BindableEnum with value of type T to compare.</param>
    /// <returns>True if the BindableEnums are not equal.</returns>
    public static bool operator !=(BindableEnum<T> x, BindableEnum<T> y)
    {
        return !(x == y);
    }

    /// <summary>
    /// Determines whether the specified object is equal to the current object.
    /// </summary>
    /// <returns>True if the specified object is equal to the current object; otherwise, false.</returns>
    /// <param name="obj">The object to compare with the current object.</param>
    public override bool Equals(object obj)
    {
        // is the object null?
        if (ReferenceEquals(obj, null))
        {
            return false;
        }

        // are they the same instance?
        if (ReferenceEquals(this, obj))
        {
            return true;
        }

        // are the values equal?
        if (obj is T)
        {
            return this.Equals((T)obj);
        }

        // are they the same type?
        if (obj.GetType() != this.GetType())
        {
            return false;
        }

        // are they otherwise equal?
        return this.Equals((BindableEnum<T>)obj);
    }

    /// <summary>
    /// Serves as a hash function for a particular type. 
    /// </summary>
    /// <returns>A hash code for the current object.</returns>
    public override int GetHashCode()
    {
        unchecked
        {
            return (EqualityComparer<T>.Default.GetHashCode(this.backingValue) * 397) ^ (this.OnValueChanged != null ? this.OnValueChanged.GetHashCode() : 0);
        }
    }

    /// <summary>
    /// Determines whether the specified object is equal to the current object.
    /// </summary>
    /// <returns>True if the specified object is equal to the current object; otherwise, false.</returns>
    /// <param name="other">The object to compare with the current object.</param>
    protected bool Equals(BindableEnum<T> other)
    {
        return this.Equals(other.backingValue) && Equals(this.OnValueChanged, other.OnValueChanged);
    }

    /// <summary>
    /// Determines whether the specified object is equal to the current object.
    /// </summary>
    /// <returns>True if the specified object is equal to the current object; otherwise, false.</returns>
    /// <param name="other">The object to compare with the current object.</param>
    protected bool Equals(T other)
    {
        return EqualityComparer<T>.Default.Equals(this.backingValue, other);
    }
}

And some basics on how to use it… Continue reading