Home > database >  one generic class for multiple variable types
one generic class for multiple variable types

Time:08-25

I would like to have the following class for multiple types (eg float, double, decimal):

internal class Progressing_Average
{
    public Progressing_Average()
    {
        _Count = 0;
        Value = 0;
    }
    public double Value { get; private set; }
    private int _Count { get; set; }
    public void AddValue(double input)
    {
        if (_Count == int.MaxValue)
        {
            throw new IndexOutOfRangeException("max amount has been reached! use preciseaverage or moving avg instead!");
        }
        _Count  ;
        Value  = (input - Value) / _Count;
    }
}

I tried with internal class Progressing_Average<T> but the issue is that i cant perform all nessesary operations on T

Operator '-' cannot be applied to operands of type 'T' and 'T'

Is there a way to do this, does it come with performance implications or should I rather use something in the lines of Processing_Average_Double

having one class for each type makes the code quite duplicate and harder to maintain i believe

CodePudding user response:

Using Lambdas and Expressions

Here is the example code you asked for

class Program
{
    static void Main(string[] args)
    {
        var avg_float = new Progressing_Average<float>();
        avg_float.AddValue(1f);
        avg_float.AddValue(2f);
        avg_float.AddValue(3f);

        Console.WriteLine($"ave={avg_float.Value:f4}");

        var avg_dec = new Progressing_Average<decimal>();
        avg_dec.AddValue(1m);
        avg_dec.AddValue(2m);
        avg_dec.AddValue(3m);

        Console.WriteLine($"ave={avg_dec.Value:c}");
    }
}

with sample output

ave=2.0000
ave=$2.00

Generic Math

Ever since NET 3.5, you can do generic math using Expressions and Lambda functions. There is a bit of setup that is needed, but the example below is fairly straightforward.

Some notes:

  • Define generic parameter T and restrict it to numeric types. Newer NET implementations have better ways of doing this but for compatibility, with .NET Framework I use the following where constraint in T

  • Define static methods (actually static anonymous functions) for the required math operations. Some basic ones are , -, *, / and the conversion from int to T for mixing _Count.

    These methods are built using a static constructor for the type and are called once for each different T used.

  • Replace the math operators with the equivalent functions. Like a b becomes Add(a,b).

Class Definition

public class Progressing_Average<T> where T: struct, 
      IComparable, 
      IComparable<T>, 
      IConvertible, 
      IEquatable<T>, 
      IFormattable
{
    static Progressing_Average()
    {
        var a = Expression.Parameter(typeof(T));
        var b = Expression.Parameter(typeof(T));
        var n = Expression.Parameter(typeof(int));

        Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(a, b), a, b).Compile();
        Sub = Expression.Lambda<Func<T, T, T>>(Expression.Subtract(a, b), a, b).Compile();
        Mul = Expression.Lambda<Func<T, T, T>>(Expression.Multiply(a, b), a, b).Compile();
        Div = Expression.Lambda<Func<T, T, T>>(Expression.Divide(a, b), a, b).Compile();
        FromInt = Expression.Lambda<Func<int, T>>(Expression.Convert(n, typeof(T)), n).Compile();
    }
    static Func<T, T, T> Add { get; }
    static Func<T, T, T> Sub { get; }
    static Func<T, T, T> Mul { get; }
    static Func<T, T, T> Div { get; }
    static Func<int, T> FromInt { get; }

    public Progressing_Average()
    {
        _Count = 0;
        Value = FromInt(0);
    }
    public T Value { get; private set; }
    private int _Count { get; set; }
    public void AddValue(T input)
    {
        if (_Count == int.MaxValue)
        {
            throw new IndexOutOfRangeException("max amount has been reached! use preciseaverage or moving avg instead!");
        }
        _Count  ;
        Value = Add(Value,  Div(Sub(input, Value), FromInt(_Count)));
    }
}

Support for User Defined Types

If you relax the constraints a bit, you can even use your own types above.

Define the processing class as

public class Progressing_Average<T> where T: struct, 
      IEquatable<T>, 
      IFormattable

and a user-defined type, such as

public readonly struct Point :
    IEquatable<Point>,
    IFormattable
{
    public Point(float x, float y) : this()
    {
        X = x;
        Y = y;
    }

    public float X { get; }
    public float Y { get; }

    public static Point operator  (Point a, Point b)
        => new Point(a.X   b.X, a.Y   b.Y);
    public static Point operator -(Point a, Point b)
        => new Point(a.X - b.X, a.Y - b.Y);
    public static Point operator *(Point a, Point b)
        => new Point(a.X * b.X, a.Y * b.Y);
    public static Point operator /(Point a, Point b)
        => new Point(a.X / b.X, a.Y / b.Y);

    public static implicit operator Point(int x)
        => new Point(x,x);
    public static implicit operator Point(float x)
        => new Point(x,x);
    public static implicit operator Point(decimal x)
        => new Point((float)x,(float)x);
    public static implicit operator Point(double x)
        => new Point((float)x,(float)x);

    #region IFormattable Members
    public string ToString(string formatting, IFormatProvider provider)
    {
        return $"({X},{Y})";
    }
    public string ToString(string formatting)
        => ToString(formatting, null);
    public override string ToString()
        => ToString("g"); 
    #endregion

    #region IEquatable Members

    /// <summary>
    /// Equality overrides from <see cref="System.Object"/>
    /// </summary>
    /// <param name="obj">The object to compare this with</param>
    /// <returns>False if object is a different type, otherwise it calls <code>Equals(Point)</code></returns>
    public override bool Equals(object obj)
    {
        return obj is Point item && Equals(item);
    }

    /// <summary>
    /// Checks for equality among <see cref="Point"/> classes
    /// </summary>
    /// <returns>True if equal</returns>
    public bool Equals(Point other)
    {
        return X.Equals(other.X)
            && Y.Equals(other.Y);
    }
    /// <summary>
    /// Calculates the hash code for the <see cref="Point"/>
    /// </summary>
    /// <returns>The int hash value</returns>
    public override int GetHashCode()
    {
        unchecked
        {
            int hc = -1817952719;
            hc = (-1521134295) * hc   X.GetHashCode();
            hc = (-1521134295) * hc   Y.GetHashCode();
            return hc;
        }
    }
    public static bool operator ==(Point target, Point other) { return target.Equals(other); }
    public static bool operator !=(Point target, Point other) { return !target.Equals(other); }

    #endregion

}

Now watch as you can use the above struct to get averages

class Program
{
    static void Main(string[] args)
    {
        var avg_vec = new Progressing_Average<Point>();
        avg_vec.AddValue(new Point(1, 2));
        avg_vec.AddValue(new Point(2, 3));
        avg_vec.AddValue(new Point(3, 4));
        Console.WriteLine($"ave={avg_vec.Value}");
    }
}

with output

ave=(2,3)

There is some skullduggery going on here, because behind the scenes the expression Expression.Convert() automatically calls any implicit operator for conversions, as well as any user defined arithmetic operators such as operator (a,b).

CodePudding user response:

In .NET 7, you'll be able to use number generics (generic math). In .NET 6, this is not possible.

https://devblogs.microsoft.com/dotnet/dotnet-7-generic-math/

In your example, you would use it like this:

internal class Progressing_Average<T> where T : INumber<T>
{
    public Progressing_Average()
    {
        _Count = 0;
        Value = default;
    }
    public T Value { get; private set; }
    private int _Count { get; set; }
    public void AddValue(T input)
    {
        if (_Count == int.MaxValue)
        {
            throw new IndexOutOfRangeException("max amount has been reached! use preciseaverage or moving avg instead!");
        }
        _Count  ;
        Value  = (input - Value) / _Count;
    }
}

CodePudding user response:

You can use Convert.ChangeType.

Here is an idea you may like to do

internal class Progressing_Average
{
    public Progressing_Average()
    {
        _Count = 0;
        Value = 0;
    }
    public double Value { get; private set; }
    private int _Count { get; set; }
    // now val could be float decimal int etc..
    public void AddValue(object val)
    {
        double input = (double)Convert.ChangeType(val, typeof(double))
        if (_Count == int.MaxValue)
        {
            throw new IndexOutOfRangeException("max amount has been reached! use preciseaverage or moving avg instead!");
        }
        _Count  ;
        Value  = (input - Value) / _Count;
    }
}
  •  Tags:  
  • c#
  • Related