patterncsharpMinor
Generic Calculator and Generic Number
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:
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:
Of coure... this makes the code less readable, so I created another struct to overcome this:
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
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
```
///
/// 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
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.