|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionWhile playing with .NET 2.0's Generics, an interesting pattern came to my mind as a solution for a code reuse problem. I quickly implemented the solution, fairly skeptical about the C# compiler being able to compile my code successfully. To my surprise, the compiler didn't complain a bit. Still thinking that even though it compiled successfully, some weird exception might be thrown at runtime, I tried executing my piece of code. It worked flawlessly. BackgroundThe problem I had to solve: public interface IDateObj
{
DateTime Date
{
get;
set;
}
void Combine(IDateObj obj);
}And the generic class for that: public static class ObjListManager<T> where T : ICloneable, IDateObj
{
public static List<T> EliminateSameDates(List<T> listOrig)
{
if (listOrig.Count < 2)
return listOrig;
List<T> list = new List<T>(listOrig.Count);
//copy listOrig to list
foreach (T obj in listOrig)
{
T objCopy = (T)obj.Clone();
list.Add(objCopy);
}
//sort list by date
list.Sort(delegate(T obj1, T obj2) { return obj1.Date.CompareTo(obj2.Date); });
for (int i = list.Count - 1; i >= 1; i--)
{
T curr = list[i];
T prev = list[i - 1];
//if it has the same date as the one before
if (prev.Date.Date == curr.Date.Date)
{
prev.Combine(curr);
prev.Date = curr.Date;
list.RemoveAt(i);
}
}
return list;
}
}Not bad. This way we could do the job like this:public class VersementObject : ICloneable, IDateObj
{
// implementation ...
public void Combine(IDateObj obj)
{
// get my object
VersementObject myObj = (VersementObject)obj;
// do whatever
}
}
void Main()
{
List<VersementObject> list;
// init list
// prepare
list = ObjListManager<VersementObject>.EliminateSameDates(list);
// do whatever with it
}No big deal so far. Nothing interesting, really. Thing is that I wanted to do better than that because:
About the codeSo if I want to solve the 4 problems I still had, the interface has to go. public abstract class ObjListManager<T> : ICloneable where T : ObjListManager<T>
{
public abstract DateTime Date
{
get;
set;
}
public abstract object Clone();
protected abstract void Combine(T obj);
delegate bool EliminateCriterion(T prev, T curr);
static List<T> Eliminate(List<T> listOrig, EliminateCriterion crit)
{
if (listOrig.Count < 2)
return listOrig;
List<T> list = new List<T>(listOrig.Count);
//copy listOrig to list
foreach (T obj in listOrig)
{
T objCopy = (T)obj.Clone();
list.Add(objCopy);
}
//sort list by date
list.Sort(delegate(T obj1, T obj2) { return obj1.Date.CompareTo(obj2.Date); });
for (int i = list.Count - 1; i >= 1; i--)
{
T curr = list[i];
T prev = list[i - 1];
//if it has the same date as the one before
if (crit(prev, curr))
{
prev.Combine(curr);
prev.Date = curr.Date;
list.RemoveAt(i);
}
}
return list;
}
public static List<T> EliminateSameDates(List<T> listOrig)
{
return Eliminate(listOrig, delegate(T prev, T curr)
{ return prev.Date.Date == curr.Date.Date; });
}
public static List<T> EliminateSameMonths(List<T> listOrig)
{
return Eliminate(listOrig, delegate(T prev, T curr)
{ return prev.Date.Year == curr.Date.Year &&
prev.Date.Month == curr.Date.Month; });
}
}And the implementation of the class becomes:public class VersementObject : ObjListManager<VersementObject>
{
// other properties, useful data
public int ID
{
get { return _id; }
set { _id = value; }
}
public override DateTime Date
{
get { return _date; }
set { _date = value; }
}
public override object Clone()
{
VersementObject obj = new VersementObject();
// copy all fields
return obj;
}
protected override void Combine(VersementObject obj)
{
// do whatever combine logic
}
public static new List<VersementObject>
EliminateSameDates(List<VersementObject> listOrig)
{
List<VersementObject> list =
ObjListManager<VersementObject>.EliminateSameDates(listOrig);
// init ID's
int vId = 0;
foreach (VersementObject vo in list)
{
vo.ID = vId++;
}
return list;
}
}And finally the way I invoke the method:list = VersementObject.EliminateSameDates(list);Neat. So that solved my problems. Including the last one. If I need to do some special processing of the list, after it got sorted and prepared, I can do it in the EliminateSameDates function by overriding it with a new implementation. In the code I have a property ID that needs to be set to the value of the position of the object in the list.As a bonus I also implemented an EliminateSameMonths method. This one eliminates all entries from the list that have their date in the same months. All classes inheriting from my ObjListManager<> have the two methods: EliminateSameDates and EliminateSameMonths, so if the logic of the application changes in time (for example all entries in the same months and not the same date are now to be removed), you can simply switch the calls.The two Eliminate- methods are very similar so I had them both call the same private method, Eliminate, with different elimination criteria. The criterion for elimination is supplied using a delegate. I also used anonymous methods for the implementation of the delegates to keep the code short and compact.After running my code successfully and the feeling of surprise and relief slowly fading away, another shadow of doubt entered my mind. What if it all works because I have an abstract class? Maybe the abstract-ness helps avoid the recursion I was worried about. So I tried it without having an abstract class. Well, it worked flawlessly again. Nice.Points of InterestThe Generics in C# 2 are pretty nice, even though they are no match for the templates in C++. Anyway, they both have pros and cons. Today, the C# compiler surprised me. I have underestimated it. Anyway, the conclusion of my little experiment is that C#'s Generics is a pretty powerful tool to optimize and embellish your code and that I should never stop at the "classic" solution. There's always an even better solution at hand. History21 March 2008 - First version of article.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||