1

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

5
  • 3
    devblogs.microsoft.com/dotnet/… Commented Aug 24, 2022 at 12:17
  • 1
    Short answer: You need to make 3 different classes. Longer answer: Wait for C#11 and you will be able to use where T : INumber<T> Commented Aug 24, 2022 at 12:31
  • do I have to name them differently or can I name them in the same way and somehow specify which one I want to use (such as with parameter based function selection) apply(int input) vs apply(double input) Commented Aug 24, 2022 at 12:34
  • 1
    @DavidG - no you don't. You can use a single generic class, and use lambdas to fill in the missing numeric operators. Commented Aug 24, 2022 at 13:08
  • @JohnAlexiou I know, but it's pretty nasty really. Much simpler, reliable and most importantly, more readable to use 3 classes. Commented Aug 24, 2022 at 13:51

3 Answers 3

2

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

Sign up to request clarification or add additional context in comments.

1 Comment

See edits, as I added support for user-defined types.
2

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;
    }
}

Comments

0

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;
    }
}

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.