I just want to make sure that my Rolling (Moving) Average class is understandable and reasonably thread safe.
My main questions are:
Is having totalCounts as a long just for a potential edge case overkill?
Is checking to see if the number of observed counts is 1 (and thus, just return the observed total), also edge case overkill?
Is there any way to minimize the locking in Add Method? (It's not a bottle neck or anything, but I'm just wondering if I might be locking that part more than needed)
--
public sealed class RollingAverage
{
public RollingAverage()
: this(defaultMaxRememberedNumbers)
{
//
}
public RollingAverage(int maxRememberedNumbers)
{
if (maxRememberedNumbers <= 0)
{
throw new ArgumentException("maxRememberedNumbersmust be greater than 0.", "maxRememberedNumbers");
}
this.counts = new Queue<int>(maxRememberedNumbers);
this.maxSize = maxRememberedNumbers;
this.currentTotal = 0L;
this.padLock = new Object();
}
private const int defaultMaxRememberedNumbers = 10;
private readonly Queue<int> counts;
private readonly int maxSize;
private long currentTotal;
private object padLock;
public void Add(int value)
{
lock (this.padLock)
{
if (this.counts.Count == this.maxSize)
{
this.currentTotal -= (long)this.counts.Dequeue();
}
this.counts.Enqueue(value);
this.currentTotal += (long)this.value;
}
}
public int CurrentAverage
{
get
{
long lenCounts;
long observedTotal;
lock (this.padLock)
{
lenCounts = (long)this.counts.Count;
observedTotal = this.currentTotal;
}
if (lenCounts == 0)
{
throw new InvalidOperationException("No counts to average.");
}
else if (lenCounts == 1)
{
return (int)observedTotal;
}
else
{
return (int)(observedTotal / lenCounts);
}
}
}
public void Clear()
{
lock (this.padLock)
{
this.currentTotal = 0L;
this.counts.Clear();
}
}
}
-Edit- Fixed inconsistent use of "this.".
int
it does not matter, but for floating-point computations, the naive formula for a rolling average is prone to unnecessary round-off errors. And this has been known for 50 years. See this. \$\endgroup\$