HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Generic Calculator and Generic Number

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
andgenericnumbercalculator

Problem

.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 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 a, T b)
{
    T result = Calculator.Add(Calculator.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. In this struct I created every operator I could think of. The code could now be revised as:

public T DifficultCalculation(Number a, Number b)
{
    Number 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

```
///
/// 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.
///
///
public static class Calculator
{
static Calculator()
{
Add = CreateDelegate(Expression.AddChecked, "Addition", true);
Subtract = CreateDelegate(Expression.SubtractChecked, "Substraction", true);
Multiply = CreateDelegate(Expression.MultiplyChecked, "Mult

Solution

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 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 interface:

public interface ICalculator
{
    /// 
    /// Adds two values of the same type.
    /// Supported by: All numeric values.
    /// 
    /// 
    /// 
    Func Add { get; }

    /// 
    /// Subtracts two values of the same type.
    /// Supported by: All numeric values.
    /// 
    /// 
    /// 
    Func Subtract { get; }

    /// 
    /// Multiplies two values of the same type.
    /// Supported by: All numeric values.
    /// 
    /// 
    /// 
    Func Multiply { get; }

    /// 
    /// Divides two values of the same type.
    /// Supported by: All numeric values.
    /// 
    /// 
    /// 
    Func Divide { get; }

    /// 
    /// Divides two values of the same type and returns the remainder.
    /// Supported by: All numeric values.
    /// 
    /// 
    /// 
    Func Modulo { get; }

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

    /// 
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// 
    /// 
    Func Plus { get; }

    /// 
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// 
    /// 
    /// 
    Func Increment { get; }

    /// 
    /// Gets the negative value of T.
    /// Supported by: All numeric values.
    /// 
    /// 
    /// 
    Func Decrement { get; }

    /// 
    /// Shifts the number to the left.
    /// Supported by: All integral types.
    /// 
    /// 
    Func LeftShift { get; }

    /// 
    /// Shifts the number to the right.
    /// Supported by: All integral types.
    /// 
    /// 
    Func RightShift { get; }

    /// 
    /// Inverts all bits inside the value.
    /// Supported by: All integral types.
    /// 
    /// 
    Func OnesComplement { get; }

    /// 
    /// Performs a bitwise OR.
    /// Supported by: All integral types.
    /// 
    /// 
    Func Or { get; }

    /// 
    /// Performs a bitwise AND
    /// Supported by: All integral types.
    /// 
    /// 
    Func And { get; }

    /// 
    /// Performs a bitwise Exclusive OR.
    /// Supported by: All integral types.
    /// 
    /// 
    Func Xor { get; }
}


Calculator class:

```
///
/// 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.
///
///
public class Calculator : ICalculator
{
private static readonly ICalculator instance = new Calculator();

///
/// Adds two values of the same type.
/// Supported by: All numeric values.
///
///
///
private readonly Func add;

///
/// Subtracts two values of the same type.
/// Supported by: All numeric values.
///
///
///
private readonly Func subtract;

///
/// Multiplies two values of the same type.
/// Supported by: All numeric values.
///
///
///
private readonly Func multiply;

///
/// Divides two values of the same type.
/// Supported by: All numeric values.
///
///
///
private readonly Func divide;

///
/// Divides two values of the same type and returns the remainder.
/// Supported by: All numeric values.
///
///
///
private readonly Func modulo;

///
/// Gets the negative value of T.
/// Supported by: All numeric values, but will throw an OverflowException on unsigned values which are not 0.
///
///
///
private readonly Func negate;

///
/// Gets the negative value of T.
/// Supported by: All numeric values.
///
///
private readonly Func plus;

///
/// Gets the negative value of T.
/// Supported by: All numeric values.
///
///
///
private readonly Func increment;

///
/// Gets the negative value of T.
/// Supported by: All numeric values.
///
///
///
private readonly Func decrement;

///
/// Shifts the number to the left.
/// Supported by: All integral types.
///
///
private readonly Func leftShift;

///
/// Shifts the number to the right.
/// Supported by: All integral types.
///
///
private readonly Func rightShift;

///
/// Inverts all

Code Snippets

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>
/// <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>
  
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)
 

Context

StackExchange Code Review Q#26022, answer score: 7

Revisions (0)

No revisions yet.