GenericList<T>, Events, Aggregator

Published 06 June 07 05:42 PM | danieb

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
}

Comments

# alfonso said on June 19, 2007 07:13 PM:

Hi Danie,

thank very much for this big piece of source code. I was writing a list with events inheriting from List<>, but the events was not triggeredwhen the list was in DataBinding.

With you GenericList the events are triggered.

# hpnwclsjdvu said on July 28, 2008 08:52 AM:

G6MGvK  <a href="exhiavypfcvw.com/.../a>, [url=http://wkzkqyxedkvq.com/]wkzkqyxedkvq[/url], [link=http://yebajydduusz.com/]yebajydduusz[/link], http://hhgphtgeywvu.com/

# kmgcam said on August 13, 2008 03:37 AM:

OW3Pai  <a href="cvtpwurxcywd.com/.../a>, [url=http://vhgtuztixfoq.com/]vhgtuztixfoq[/url], [link=http://rjqhhcoakuew.com/]rjqhhcoakuew[/link], http://rlazyvxyoaim.com/

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above: