15
\$\begingroup\$

.NET does not support generic numbers. It is not possible to enforce a generic method with generic argument T that T is a number. The following code will simply not compile:

public T DifficultCalculation<T>(T a, T b)
{
    T result = a * b + a; // <== WILL NOT COMPILE!
    return result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Should result in 8.

To overcome this problem I created a class to overcome this, a generic Calculator. Using this calculator, you can do arithmatic operations on a generic type. It is at that moment assumed that the programmer knows what he is doing. The usage would look like:

public T DifficultCalculation<T>(T a, T b)
{
    T result = Calculator<T>.Add(Calculator<T>.Multiply(a, b), a);
    return result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.

Of coure... this makes the code less readable, so I created another struct to overcome this: Number<T>. In this struct I created every operator I could think of. The code could now be revised as:

public T DifficultCalculation<T>(Number<T> a, Number<T> b)
{
    Number<T> result = a * b + a;
    return (T)result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.

I think this can be very handy for some other developers. Before I broadcast it to the world, I want it to be reviewed. If you have any comments about it (about functionality, performance, usability), please let me know!

Calculator

/// <summary>
/// Class to allow operations (like Add, Multiply, etc.) for generic types. This type should allow these operations themselves.
/// If a type does not support an operation, an exception is throw when using this operation, not during construction of this class.
/// </summary>
/// <typeparam name="T"></typeparam>
public static class Calculator<T>
{
    static Calculator()
    {
        Add = CreateDelegate<T>(Expression.AddChecked, "Addition", true);
        Subtract = CreateDelegate<T>(Expression.SubtractChecked, "Substraction", true);
        Multiply = CreateDelegate<T>(Expression.MultiplyChecked, "Multiply", true);
        Divide = CreateDelegate<T>(Expression.Divide, "Divide", true);
        Modulo = CreateDelegate<T>(Expression.Modulo, "Modulus", true);
        Negate = CreateDelegate(Expression.NegateChecked, "Negate", true);
        Plus = CreateDelegate(Expression.UnaryPlus, "Plus", true);
        Increment = CreateDelegate(Expression.Increment, "Increment", true);
        Decrement = CreateDelegate(Expression.Decrement, "Decrement", true);
        LeftShift = CreateDelegate<int>(Expression.LeftShift, "LeftShift", false);
        RightShift = CreateDelegate<int>(Expression.RightShift, "RightShift", false);
        OnesComplement = CreateDelegate(Expression.OnesComplement, "OnesComplement",  false);
        And = CreateDelegate<T>(Expression.And, "BitwiseAnd", false);
        Or = CreateDelegate<T>(Expression.Or, "BitwiseOr", false);
        Xor = CreateDelegate<T>(Expression.ExclusiveOr, "ExclusiveOr", false);
    }

    static private Func<T, T2, T> CreateDelegate<T2>(Func<Expression, Expression, Expression> @operator, string operatorName, bool isChecked)
    {
        try
        {
            Type convertToTypeA = ConvertTo(typeof(T));
            Type convertToTypeB = ConvertTo(typeof(T2));
            ParameterExpression parameterA = Expression.Parameter(typeof(T), "a");
            ParameterExpression parameterB = Expression.Parameter(typeof(T2), "b");
            Expression valueA = (convertToTypeA != null) ? Expression.Convert(parameterA, convertToTypeA) : (Expression)parameterA;
            Expression valueB = (convertToTypeB != null) ? Expression.Convert(parameterB, convertToTypeB) : (Expression)parameterB;
            Expression body = @operator(valueA, valueB);
            if (convertToTypeA != null)
            {
                if (isChecked)
                    body = Expression.ConvertChecked(body, typeof(T));
                else
                    body = Expression.Convert(body, typeof(T));
            }
            return Expression.Lambda<Func<T, T2, T>>(body, parameterA, parameterB).Compile();
        }
        catch
        {
            return (a, b) =>
            {
                throw new InvalidOperationException("Operator " + operatorName + " is not supported by type " + typeof(T).FullName + ".");
            };
        }
    }

    static private Func<T, T> CreateDelegate(Func<Expression, Expression> @operator, string operatorName, bool isChecked)
    {
        try
        {
            Type convertToType = ConvertTo(typeof(T));
            ParameterExpression parameter = Expression.Parameter(typeof(T), "a");
            Expression value = (convertToType != null) ? Expression.Convert(parameter, convertToType) : (Expression)parameter;
            Expression body = @operator(value);
            if (convertToType != null)
            {
                if (isChecked)
                    body = Expression.ConvertChecked(body, typeof(T));
                else
                    body = Expression.Convert(body, typeof(T));
            }
            return Expression.Lambda<Func<T, T>>(body, parameter).Compile();
        }
        catch 
        {
            return (a) =>
            {
                throw new InvalidOperationException("Operator " + operatorName + " is not supported by type " + typeof(T).FullName + ".");
            };
        }
    }

    static private Type ConvertTo(Type type)
    {
        switch (Type.GetTypeCode(type))
        {
            case TypeCode.Char:
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.UInt16:
                return typeof(int);
        }
        return null;
    }

    /// <summary>
    /// Adds two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T, T> Add;

    /// <summary>
    /// Subtracts two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T, T> Subtract;

    /// <summary>
    /// Multiplies two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T, T> Multiply;

    /// <summary>
    /// Divides two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T, T> Divide;

    /// <summary>
    /// Divides two values of the same type and returns the remainder.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T, T> Modulo;

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values, but will throw an OverflowException on unsigned values which are not 0.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T> Negate;

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T> Plus;

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T> Increment;

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T> Decrement;

    /// <summary>
    /// Shifts the number to the left.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, int, T> LeftShift;

    /// <summary>
    /// Shifts the number to the right.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, int, T> RightShift;

    /// <summary>
    /// Inverts all bits inside the value.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T> OnesComplement;

    /// <summary>
    /// Performs a bitwise OR.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T, T> Or;

    /// <summary>
    /// Performs a bitwise AND
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T, T> And;

    /// <summary>
    /// Performs a bitwise Exclusive OR.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public static readonly Func<T, T, T> Xor;
}

Number

public struct Number<T>
    where T : IComparable<T>, IEquatable<T>
{
    private readonly T _Value;

    public Number(T value)
    {
        _Value = value;
    }

    #region Comparison

    public bool Equals(Number<T> other)
    {
        return _Value.Equals(other._Value);
    }

    public bool Equals(T other)
    {
        return _Value.Equals(other);
    }

    public int CompareTo(Number<T> other)
    {
        return _Value.CompareTo(other._Value);
    }

    public int CompareTo(T other)
    {
        return _Value.CompareTo(other);
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        if (obj is T)
            return _Value.Equals((T)obj);
        if (obj is Number<T>)
            return _Value.Equals(((Number<T>)obj)._Value);
        return false;
    }

    public override int GetHashCode()
    {
        return (_Value == null) ? 0 : _Value.GetHashCode();
    }

    static public bool operator ==(Number<T> a, Number<T> b)
    {
        return a._Value.Equals(b._Value);
    }

    static public bool operator !=(Number<T> a, Number<T> b)
    {
        return !a._Value.Equals(b._Value);
    }

    static public bool operator <(Number<T> a, Number<T> b)
    {
        return a._Value.CompareTo(b._Value) < 0;
    }

    static public bool operator <=(Number<T> a, Number<T> b)
    {
        return a._Value.CompareTo(b._Value) <= 0;
    }

    static public bool operator >(Number<T> a, Number<T> b)
    {
        return a._Value.CompareTo(b._Value) > 0;
    }

    static public bool operator >=(Number<T> a, Number<T> b)
    {
        return a._Value.CompareTo(b._Value) >= 0;
    }

    static public Number<T> operator !(Number<T> a)
    {
        return new Number<T>(Calculator<T>.Negate(a._Value));
    }

    #endregion
    #region Arithmatic operations

    static public Number<T> operator +(Number<T> a, Number<T> b)
    {
        return new Number<T>(Calculator<T>.Add(a._Value, b._Value));
    }

    static public Number<T> operator -(Number<T> a, Number<T> b)
    {
        return new Number<T>(Calculator<T>.Subtract(a._Value, b._Value));
    }

    static public Number<T> operator *(Number<T> a, Number<T> b)
    {
        return new Number<T>(Calculator<T>.Multiply(a._Value, b._Value));
    }

    static public Number<T> operator /(Number<T> a, Number<T> b)
    {
        return new Number<T>(Calculator<T>.Divide(a._Value, b._Value));
    }

    static public Number<T> operator %(Number<T> a, Number<T> b)
    {
        return new Number<T>(Calculator<T>.Modulo(a._Value, b._Value));
    }

    static public Number<T> operator -(Number<T> a)
    {
        return new Number<T>(Calculator<T>.Negate(a._Value));
    }

    static public Number<T> operator +(Number<T> a)
    {
        return new Number<T>(Calculator<T>.Plus(a._Value));
    }

    static public Number<T> operator ++(Number<T> a)
    {
        return new Number<T>(Calculator<T>.Increment(a._Value));
    }

    static public Number<T> operator --(Number<T> a)
    {
        return new Number<T>(Calculator<T>.Decrement(a._Value));
    }

    #endregion 
    #region Bitwise operations

    static public Number<T> operator <<(Number<T> a, int b)
    {
        return new Number<T>(Calculator<T>.LeftShift(a._Value, b));
    }

    static public Number<T> operator >>(Number<T> a, int b)
    {
        return new Number<T>(Calculator<T>.RightShift(a._Value, b));
    }

    static public Number<T> operator &(Number<T> a, Number<T> b)
    {
        return new Number<T>(Calculator<T>.And(a._Value, b._Value));
    }

    static public Number<T> operator |(Number<T> a, Number<T> b)
    {
        return new Number<T>(Calculator<T>.Or(a._Value, b._Value));
    }

    static public Number<T> operator ^(Number<T> a, Number<T> b)
    {
        return new Number<T>(Calculator<T>.Xor(a._Value, b._Value));
    }

    static public Number<T> operator ~(Number<T> a)
    {
        return new Number<T>(Calculator<T>.OnesComplement(a._Value));
    }

    #endregion
    #region Casts

    static public implicit operator Number<T>(T value)
    {
        return new Number<T>(value);
    }

    static public explicit operator T(Number<T> value)
    {
        return value._Value;
    }

    #endregion
    #region Other members

    public override string ToString()
    {
        return (_Value == null) ? string.Empty : _Value.ToString();
    }

    #endregion
}
\$\endgroup\$
2
  • \$\begingroup\$ It is not currently possible to multiply a double with an integer with automatic widening. \$\endgroup\$
    – kerem
    Commented Feb 24, 2015 at 13:53
  • 1
    \$\begingroup\$ @Kerem: Correct. Calculation with different numeric datatypes does not work. It only works when the types 'T' are the same. I am open for suggestions. \$\endgroup\$ Commented Mar 4, 2015 at 10:35

2 Answers 2

7
\$\begingroup\$

This is a nifty implementation! Only one thought I came up with: I think Number is too tightly coupled with Calculator. Now I couldn't decouple it completely, due to the operator overloads in Number, but I think I made it such that you could sub in mock or different Calculators as needed. I also added struct as a generic constraint on Number<T> so that null checks weren't needed. I don't know of any numerics that aren't represented as structs, so I think that's a reasonable change.

ICalculator<T> interface:

public interface ICalculator<T>
{
    /// <summary>
    /// Adds two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T, T> Add { get; }

    /// <summary>
    /// Subtracts two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T, T> Subtract { get; }

    /// <summary>
    /// Multiplies two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T, T> Multiply { get; }

    /// <summary>
    /// Divides two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T, T> Divide { get; }

    /// <summary>
    /// Divides two values of the same type and returns the remainder.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T, T> Modulo { get; }

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values, but will throw an OverflowException on unsigned values which are not 0.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T> Negate { get; }

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T> Plus { get; }

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T> Increment { get; }

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T> Decrement { get; }

    /// <summary>
    /// Shifts the number to the left.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    Func<T, int, T> LeftShift { get; }

    /// <summary>
    /// Shifts the number to the right.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    Func<T, int, T> RightShift { get; }

    /// <summary>
    /// Inverts all bits inside the value.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T> OnesComplement { get; }

    /// <summary>
    /// Performs a bitwise OR.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T, T> Or { get; }

    /// <summary>
    /// Performs a bitwise AND
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T, T> And { get; }

    /// <summary>
    /// Performs a bitwise Exclusive OR.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    Func<T, T, T> Xor { get; }
}

Calculator<T> class:

/// <summary>
/// Class to allow operations (like Add, Multiply, etc.) for generic types. This type should allow these operations themselves.
/// If a type does not support an operation, an exception is throw when using this operation, not during construction of this class.
/// </summary>
/// <typeparam name="T"></typeparam>
public class Calculator<T> : ICalculator<T>
{
    private static readonly ICalculator<T> instance = new Calculator<T>();

    /// <summary>
    /// Adds two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T, T> add;

    /// <summary>
    /// Subtracts two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T, T> subtract;

    /// <summary>
    /// Multiplies two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T, T> multiply;

    /// <summary>
    /// Divides two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T, T> divide;

    /// <summary>
    /// Divides two values of the same type and returns the remainder.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T, T> modulo;

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values, but will throw an OverflowException on unsigned values which are not 0.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T> negate;

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T> plus;

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T> increment;

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T> decrement;

    /// <summary>
    /// Shifts the number to the left.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, int, T> leftShift;

    /// <summary>
    /// Shifts the number to the right.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, int, T> rightShift;

    /// <summary>
    /// Inverts all bits inside the value.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T> onesComplement;

    /// <summary>
    /// Performs a bitwise OR.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T, T> or;

    /// <summary>
    /// Performs a bitwise AND
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T, T> and;

    /// <summary>
    /// Performs a bitwise Exclusive OR.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    private readonly Func<T, T, T> xor;

    public Calculator()
    {
        this.add = CreateDelegate<T>(Expression.AddChecked, "Addition", true);
        this.subtract = CreateDelegate<T>(Expression.SubtractChecked, "Substraction", true);
        this.multiply = CreateDelegate<T>(Expression.MultiplyChecked, "Multiply", true);
        this.divide = CreateDelegate<T>(Expression.Divide, "Divide", true);
        this.modulo = CreateDelegate<T>(Expression.Modulo, "Modulus", true);
        this.negate = CreateDelegate(Expression.NegateChecked, "Negate", true);
        this.plus = CreateDelegate(Expression.UnaryPlus, "Plus", true);
        this.increment = CreateDelegate(Expression.Increment, "Increment", true);
        this.decrement = CreateDelegate(Expression.Decrement, "Decrement", true);
        this.leftShift = CreateDelegate<int>(Expression.LeftShift, "LeftShift", false);
        this.rightShift = CreateDelegate<int>(Expression.RightShift, "RightShift", false);
        this.onesComplement = CreateDelegate(Expression.OnesComplement, "OnesComplement", false);
        this.and = CreateDelegate<T>(Expression.And, "BitwiseAnd", false);
        this.or = CreateDelegate<T>(Expression.Or, "BitwiseOr", false);
        this.xor = CreateDelegate<T>(Expression.ExclusiveOr, "ExclusiveOr", false);
    }

    public static ICalculator<T> Instance
    {
        get
        {
            return instance;
        }
    }

    /// <summary>
    /// Adds two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T, T> Add
    {
        get
        {
            return this.add;
        }
    }

    /// <summary>
    /// Subtracts two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T, T> Subtract
    {
        get
        {
            return this.subtract;
        }
    }

    /// <summary>
    /// Multiplies two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T, T> Multiply
    {
        get
        {
            return this.multiply;
        }
    }

    /// <summary>
    /// Divides two values of the same type.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T, T> Divide
    {
        get
        {
            return this.divide;
        }
    }

    /// <summary>
    /// Divides two values of the same type and returns the remainder.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T, T> Modulo
    {
        get
        {
            return this.modulo;
        }
    }

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values, but will throw an OverflowException on unsigned values which are not 0.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T> Negate
    {
        get
        {
            return this.negate;
        }
    }

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T> Plus
    {
        get
        {
            return this.plus;
        }
    }

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T> Increment
    {
        get
        {
            return this.increment;
        }
    }

    /// <summary>
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// </summary>
    /// <exception cref="OverflowException"/>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T> Decrement
    {
        get
        {
            return this.decrement;
        }
    }

    /// <summary>
    /// Shifts the number to the left.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, int, T> LeftShift
    {
        get
        {
            return this.leftShift;
        }
    }

    /// <summary>
    /// Shifts the number to the right.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, int, T> RightShift
    {
        get
        {
            return this.rightShift;
        }
    }

    /// <summary>
    /// Inverts all bits inside the value.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T> OnesComplement
    {
        get
        {
            return this.onesComplement;
        }
    }

    /// <summary>
    /// Performs a bitwise OR.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T, T> Or
    {
        get
        {
            return this.or;
        }
    }

    /// <summary>
    /// Performs a bitwise AND
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T, T> And
    {
        get
        {
            return this.and;
        }
    }

    /// <summary>
    /// Performs a bitwise Exclusive OR.
    /// Supported by: All integral types.
    /// </summary>
    /// <exception cref="InvalidOperationException"/>
    public Func<T, T, T> Xor
    {
        get
        {
            return this.xor;
        }
    }

    private static Func<T, T2, T> CreateDelegate<T2>(Func<Expression, Expression, Expression> @operator, string operatorName, bool isChecked)
    {
        try
        {
            var convertToTypeA = ConvertTo(typeof(T));
            var convertToTypeB = ConvertTo(typeof(T2));
            var parameterA = Expression.Parameter(typeof(T), "a");
            var parameterB = Expression.Parameter(typeof(T2), "b");
            var valueA = (convertToTypeA != null) ? Expression.Convert(parameterA, convertToTypeA) : (Expression)parameterA;
            var valueB = (convertToTypeB != null) ? Expression.Convert(parameterB, convertToTypeB) : (Expression)parameterB;
            var body = @operator(valueA, valueB);

            if (convertToTypeA != null)
            {
                body = isChecked ? Expression.ConvertChecked(body, typeof(T)) : Expression.Convert(body, typeof(T));
            }

            return Expression.Lambda<Func<T, T2, T>>(body, parameterA, parameterB).Compile();
        }
        catch
        {
            return (a, b) =>
            {
                throw new InvalidOperationException("Operator " + operatorName + " is not supported by type " + typeof(T).FullName + ".");
            };
        }
    }

    private static Func<T, T> CreateDelegate(Func<Expression, Expression> @operator, string operatorName, bool isChecked)
    {
        try
        {
            var convertToType = ConvertTo(typeof(T));
            var parameter = Expression.Parameter(typeof(T), "a");
            var value = (convertToType != null) ? Expression.Convert(parameter, convertToType) : (Expression)parameter;
            var body = @operator(value);

            if (convertToType != null)
            {
                body = isChecked ? Expression.ConvertChecked(body, typeof(T)) : Expression.Convert(body, typeof(T));
            }

            return Expression.Lambda<Func<T, T>>(body, parameter).Compile();
        }
        catch
        {
            return a =>
            {
                throw new InvalidOperationException("Operator " + operatorName + " is not supported by type " + typeof(T).FullName + ".");
            };
        }
    }

    private static Type ConvertTo(Type type)
    {
        switch (Type.GetTypeCode(type))
        {
            case TypeCode.Char:
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.UInt16:
                return typeof(int);
        }

        return null;
    }
}

Number<T> class:

public struct Number<T> where T : struct, IComparable<T>, IEquatable<T>
{
    private static readonly ICalculator<T> defaultCalculator = Calculator<T>.Instance;

    private readonly T value;

    public Number(T value)
    {
        this.value = value;
    }

    public bool Equals(Number<T> other)
    {
        return this.value.Equals(other.value);
    }

    public bool Equals(T other)
    {
        return this.value.Equals(other);
    }

    public int CompareTo(Number<T> other)
    {
        return this.value.CompareTo(other.value);
    }

    public int CompareTo(T other)
    {
        return this.value.CompareTo(other);
    }

    public override bool Equals(object obj)
    {
        return obj != null && (obj is T
            ? this.value.Equals((T)obj)
            : obj is Number<T> && this.value.Equals(((Number<T>)obj).value));
    }

    public override int GetHashCode()
    {
        return this.value.GetHashCode();
    }

    public static bool operator ==(Number<T> a, Number<T> b)
    {
        return a.value.Equals(b.value);
    }

    public static bool operator !=(Number<T> a, Number<T> b)
    {
        return !a.value.Equals(b.value);
    }

    public static bool operator <(Number<T> a, Number<T> b)
    {
        return a.value.CompareTo(b.value) < 0;
    }

    public static bool operator <=(Number<T> a, Number<T> b)
    {
        return a.value.CompareTo(b.value) <= 0;
    }

    public static bool operator >(Number<T> a, Number<T> b)
    {
        return a.value.CompareTo(b.value) > 0;
    }

    public static bool operator >=(Number<T> a, Number<T> b)
    {
        return a.value.CompareTo(b.value) >= 0;
    }

    public static Number<T> operator !(Number<T> a)
    {
        return new Number<T>(defaultCalculator.Negate(a.value));
    }

    public static Number<T> operator +(Number<T> a, Number<T> b)
    {
        return new Number<T>(defaultCalculator.Add(a.value, b.value));
    }

    public static Number<T> operator -(Number<T> a, Number<T> b)
    {
        return new Number<T>(defaultCalculator.Subtract(a.value, b.value));
    }

    public static Number<T> operator *(Number<T> a, Number<T> b)
    {
        return new Number<T>(defaultCalculator.Multiply(a.value, b.value));
    }

    public static Number<T> operator /(Number<T> a, Number<T> b)
    {
        return new Number<T>(defaultCalculator.Divide(a.value, b.value));
    }

    public static Number<T> operator %(Number<T> a, Number<T> b)
    {
        return new Number<T>(defaultCalculator.Modulo(a.value, b.value));
    }

    public static Number<T> operator -(Number<T> a)
    {
        return new Number<T>(defaultCalculator.Negate(a.value));
    }

    public static Number<T> operator +(Number<T> a)
    {
        return new Number<T>(defaultCalculator.Plus(a.value));
    }

    public static Number<T> operator ++(Number<T> a)
    {
        return new Number<T>(defaultCalculator.Increment(a.value));
    }

    public static Number<T> operator --(Number<T> a)
    {
        return new Number<T>(defaultCalculator.Decrement(a.value));
    }

    public static Number<T> operator <<(Number<T> a, int b)
    {
        return new Number<T>(defaultCalculator.LeftShift(a.value, b));
    }

    public static Number<T> operator >>(Number<T> a, int b)
    {
        return new Number<T>(defaultCalculator.RightShift(a.value, b));
    }

    public static Number<T> operator &(Number<T> a, Number<T> b)
    {
        return new Number<T>(defaultCalculator.And(a.value, b.value));
    }

    public static Number<T> operator |(Number<T> a, Number<T> b)
    {
        return new Number<T>(defaultCalculator.Or(a.value, b.value));
    }

    public static Number<T> operator ^(Number<T> a, Number<T> b)
    {
        return new Number<T>(defaultCalculator.Xor(a.value, b.value));
    }

    public static Number<T> operator ~(Number<T> a)
    {
        return new Number<T>(defaultCalculator.OnesComplement(a.value));
    }

    public static implicit operator Number<T>(T value)
    {
        return new Number<T>(value);
    }

    public static explicit operator T(Number<T> value)
    {
        return value.value;
    }

    public override string ToString()
    {
        return this.value.ToString();
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ Monad? I don't see any monad in there. The definition of monad is that it has bind and return functions that behave in a specific way and there is nothing like that here. \$\endgroup\$
    – svick
    Commented May 10, 2013 at 17:21
  • \$\begingroup\$ @svick you're right. \$\endgroup\$ Commented May 10, 2013 at 19:18
  • \$\begingroup\$ @Jesse: Thank for you review. I greatly appreciate it. I also though about put the struct as a contraint. But what if somebody decides to create his own numeric class (like a vector, or complex or whatever. And he does not create a struct but a class. Than this system would not work anymore. So I left it out, because it costs only one compare with null (if I remember correctly). Can you please explain to me how your code is losely coupled? \$\endgroup\$ Commented May 10, 2013 at 19:25
  • \$\begingroup\$ @MartinMulder it's looser - not completely decoupled. The key is creating an interface out of Calculator. My line private static readonly ICalculator<T> defaultCalculator = Calculator<T>.Instance; is what keeps it coupled to the implementation. \$\endgroup\$ Commented May 10, 2013 at 19:28
2
\$\begingroup\$
  1. Have you considered using Operator from MiscUtil instead of your Calculator?
  2. You should never use general catch. Instead, catch only the specific exception you're expecting.
  3. Since operatorName is used just for the exception, I would consider getting the name from the operator's Method.
  4. _Value probably should be exposed as a read-only property, since the cast is not very discoverable.
\$\endgroup\$
2
  • \$\begingroup\$ 1. Good one: Did not know about it. And since generic numbers is a common problem, I guess more people don't. 2. You are absolutely right. I will try to find out which exception is thrown if an operator is not supported. 3. I ve choosen to use the names op .NET (like op_Modulus) without the prefix. These name are sometimes different. 4. Why does it has to be exposed? Do you know about the internals of a TimeSpan or DateTime? If someone wants to access the internal value, he can cast the Number to the original type. \$\endgroup\$ Commented May 10, 2013 at 17:43
  • \$\begingroup\$ @MartinMulder Re 4. this is not about exposing internals, as you pointed out, you can already access the value using the cast. But I think it's a good idea to have an alternative way. If you have a Number<int>, it's usually simpler to find out that there is a Value property than to find that the cast works. Also, some .Net languages (like F#) treat casts differently, though that might not be relevant to you. \$\endgroup\$
    – svick
    Commented May 10, 2013 at 18:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.