GenericList<T>, Events, Aggregator
Download the sourcecode here
I found this custom class I pieced together quite useful. It's essentially just a wrapper for List<T>, but with some added functionality.
So I added the following events that also supports "Cancel" *
BeforeAdd *
ItemAdded
BeforeItemInsert *
ItemInserted
BeforeRemove *
ItemRemoved
BeforeRemoveAt *
ItemRemovedAt
BeforeClear *
DistinctList<T>
The cancel events allowed me to create "DistinctList<T>" which is quite handy.
Notice that T has to implement IComparable since the Contains evaluates the "CompareTo" on T to determine if it's contained in the list. (This is crucial for custom objects)
///
<summary>
/// A Disctinct list of items
/// The list will automatically check that no duplicates are added to the collection
///
</summary>
///
<typeparam name="T"></typeparam>
public
class
DistinctList<T> : GenericList<T>
where T : IComparable
{
protected
override
bool OnBeforeItemInsert(int index, T item)
{
if (!this.Contains(item))
{
return
base.OnBeforeItemInsert(index, item);
}
return
true;
}
protected
override
bool OnBeforeAdd(T item)
{
if (!this.Contains(item))
{
return
base.OnBeforeAdd(item);
}
return
true;
}
}
Aggregation
I know Linq will make this obsolete since it supports aggregation but in 2.0 how many times have you written similar code?
List<int> list = new
List<int>(new
int[] { 70, 20 });
int total = 0;
foreach (int i in list)
total += i;
Assert.AreEqual(((double)total) / ((double)list.Count), 45.0);
Is this not much easier?
GenericList<int> list = new
GenericList<int>(new
int[] { 70, 20 });
Assert.AreEqual(list.Average(), 45.0);
Or on Complex types like person:
class
Person : IComparable
{
public Person(string name, int age) { this.Name = name; this.Age = age; }
public
string Name;
public
int Age;
public
int CompareTo(object obj)
{
return
this.Name.CompareTo(((Person)obj).Name);
}
}
We can simply do this:
GenericList<Person> list = new
GenericList<Person>(new
Person[]{
new
Person("Old Guy",70)
,new
Person("Young Guy",20)});
Assert.AreEqual(list.Average("Age"), 45.0);
So how does it work? I have pieced together an Aggregation helper that uses the (+) and (-) operators.
public
static T Aggregate<T>(Aggregators aggregator, IEnumerable list, string propertyName)
where T : IComparable
{
T ret = default(T);
int count = 0;
foreach (object item in list)
{
T value = AggregationHelper.GetValue<T>(item, propertyName);
switch (aggregator)
{
case
Aggregators.avg:
case
Aggregators.sum:
ret = GenericWrapper<T>.Sum(ret, value);
break;
case
Aggregators.max:
if (ret.CompareTo(value) > 0)
ret = value;
break;
case
Aggregators.min:
if (ret.CompareTo(value) < 0)
ret = value;
break;
default:
throw
new
NotImplementedException(
string.Format("Aggregator '{0}' not implemented"
, aggregator.ToString()));
}
count++;
}
if (aggregator == Aggregators.avg)
{
if (ret is
IConvertible)
{
double total = (double)System.Convert.ChangeType(ret, typeof(double));
double avg = total / (double)count;
ret = (T)System.Convert.ChangeType(avg, typeof(T));
}
else
{
throw
new
NotImplementedException("Type should be IConvertable when using average");
}
}
return ret;
}
The GetValue<T> will either simply return the value or use reflection for complex types to get the value i.e. Person.Age.
For the GenericWrapper<T> I used Lightweight Code Generation (LCG) for operator overloading as shown in this example by Keith Farmer.
Basically what this does it is allows me to call the +,- operators on any type without having to cast.
class
GenericWrapper<T> : IComparable, IComparable<T>
where T : IComparable
{
public GenericWrapper(T value) { _value = value; }
private T _value;
public T Value
{
get { return _value; }
set { _value = value; }
}
#region Opperators
///
<summary>
/// cached copy of the Add<T,T> delegate
///
</summary>
private
static
BinaryOperator<T, T, T> addTT;
///
<summary>
/// cached copy of the Subtract<T,T> delegate
///
</summary>
private
static
BinaryOperator<T, T, T> subTT;
///
<summary>
/// overloaded addition operator
/// This will use GenericOperatorFactory to create the Add<T,T> delegate
///
</summary>
///
<param name="p1"></param>
///
<param name="p2"></param>
///
<returns></returns>
public
static
GenericWrapper<T> operator +(GenericWrapper<T> p1, GenericWrapper<T> p2)
{
// use addTT to cache the delegate locally
if (addTT == null)
{
addTT = GenericOperatorFactory<T, T, T, GenericWrapper<T>>.Add;
}
return
new
GenericWrapper<T>(addTT(p1.Value, p2.Value));
}
///
<summary>
/// overloaded subtraction operator
/// This will use GenericOperatorFactory to create the Subtract<T,T> delegate
///
</summary>
///
<param name="p1"></param>
///
<param name="p2"></param>
///
<returns></returns>
public
static
GenericWrapper<T> operator -(GenericWrapper<T> p1, GenericWrapper<T> p2)
{
// use subTT to cache the delegate locally
if (subTT == null)
{
subTT = GenericOperatorFactory<T, T, T, GenericWrapper<T>>.Subtract;
}
return
new
GenericWrapper<T>(subTT(p1.Value, p2.Value));
}
#endregion
public
virtual
int CompareTo(object other)
{
return
this.Value.CompareTo((T)(other));
}
public
virtual
int CompareTo(T other)
{
return
this.Value.CompareTo(other);
}
#region Sum
public
static T Sum(T x, T y)
{
GenericWrapper<T> tmp = (new
GenericWrapper<T>(x) + new
GenericWrapper<T>(y));
return tmp.Value;
}
#endregion
}