Click here to Skip to main content
Click here to Skip to main content

Interesting Use of Generics in C# 2

By , 20 Mar 2008
 

Introduction

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

Background

The problem I had to solve:

The project I was working on contained several "data holder"-type classes that were similar in their purpose. These classes were all used to store different data. These data were associated with a date and had to be transmitted to another layer of the application as a list. This layer that was to receive the lists of the objects had two requirements: the data had to be sorted chronologically (by the date) and there could be no two pieces of data in a list having the same date.

So I have my classes, all having different properties to store the useful data, plus the Date property. Then I have my List<>'s of instances of these classes. These must be sorted of course and the entries having the same dates must be eliminated. The eliminated entries don't just disappear, since we don't want to lose the data they're holding. Therefore we must combine the data of the removed entry with the data of the entry that stays in the list.

OK, so now I had a static function that would take as input parameter a strongly-typed generic list of a particular class and return a list usable for my other layer. Since I wanted everything to be strongly-typed, I had to copy-paste-edit this function for every class. Not nice, so enter Generics.

The classic solution:

So how would a regular developer solve this? Create an interface, make the data holder classes implement the interface. Create a generic class having a parameter that inherits from the interface, have the sort / eliminate dates code work with the generic parameter. Something like:

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:
  1. In VersementObject.Combine I have a parameter of type IDateObj. But what I really want is a VersementObject, hence I need a conversion. I want to get rid of that.
  2. The VersementObject.Combine function must be public because it is an implementation of the IDateObj interface. But this function is only used as a trait for the VersementObject class in the EliminateSameDates function, I don't need it to be visible for all users of the VersementObject class.
  3. I don't like long code lines, they're harder to follow when they don't fit in the editing window. With Generics it is inevitable to have long code lines (unless you name your classes using three-lettered acronyms). But still, I'd like to make the line containing the invocation of EliminateSameDates as short as possible.
  4. What if I need to do additional processing after the objects having the same date are eliminated? All I can do is write a new function that calls EliminateSameDates and then does the processing on the list. Flexible, but not so nice. I don't want to write wrappers in the Main code. I could add a method to the interface and have EliminateSameDates call it, as a last step before returning. This would force all implementers of IDateObj to provide an empty stub for this method. And a public one, too! A lot of useless code polluting my nice classes.

About the code

So if I want to solve the 4 problems I still had, the interface has to go.
Now, the interface refuses to go away quietly because I need the Date property in the EliminateSameDates function. I also need Combine. So what's the next best thing after interfaces? Abstract classes. Actually, they're better than interfaces since you can put some code in them as well, not only stubs. Of course, the big drawback is that you can only inherit from one such class at a time because C# doesn't allow multiple inheritance (note: C++ rules! ;) ). But this time, an abstract class should do perfectly well.

So, for this to work I need a generic abstract class that would only accept as a generic parameter a type derived from itself. Nice. In theory. I was thinking that the only problem with this will be that the compiler won't see the beauty of my solution. Why? Well, let's see. The fact that I use the class itself to specify constraints about the generic parameter of the very class I was using in the definition. It looked like recursion, the nasty kind that never stops.

I was wrong. The compiler was smarter than I expected. So here's the solution:

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 Interest

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

History

21 March 2008 - First version of article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Antoniu-Gabriel Rozsa
Romania Romania
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralSome thoughts about alternativesmemberUrs Enzler25 Mar '08 - 8:38 
1. In VersementObject.Combine I have a parameter of type IDateObj. But what I really want is a VersementObject, hence I need a conversion. I want to get rid of that.
 
Why not use a Generic interface? Okay that would not be super sexy but would do the same job.
 
2. The VersementObject.Combine function must be public because it is an implementation of the IDateObj interface. But this function is only used as a trait for the VersementObject class in the EliminateSameDates function, I don't need it to be visible for all users of the VersementObject class.
 
You could use different interfaces for different "clients" of your classes.
 
3. I don't like long code lines, they're harder to follow when they don't fit in the editing window. With Generics it is inevitable to have long code lines (unless you name your classes using three-lettered acronyms). But still, I'd like to make the line containing the invocation of EliminateSameDates as short as possible.
 
You can use aliases for types: using MyAliasedType = MyGenericType>;
 
4. What if I need to do additional processing after the objects having the same date are eliminated? All I can do is write a new function that calls EliminateSameDates and then does the processing on the list. Flexible, but not so nice. I don't want to write wrappers in the Main code. I could add a method to the interface and have EliminateSameDates call it, as a last step before returning. This would force all implementers of IDateObj to provide an empty stub for this method. And a public one, too! A lot of useless code polluting my nice classes.
 
Why not use a "controller" that does the real work, instead of the data holder. To seperate the data and its operation from the operation of making the data fit a client.
 

 
Just some thoughts.
 
Urs
 
-^-^-^-^-^-^-^-
no risk no funk

GeneralRe: Some thoughts about alternativesmemberGabriel Rozsa25 Mar '08 - 10:40 
1. Yes.
2. Correct. However, that would increase complexity. I was given a bunch of "dumb" VersementObject-like classes on which I had to perform similar tasks. It is important to rewrite as little of the code as possible in order to avoid extensive testing of different modules but still think about future uses of the classes. And the best class to use is a simple class. Smile | :)
3. Yes. Still, this involves some additional typing and the code is less clear when one reads it.
4. Again, I wanted to keep it simple as much as possible. Obviously there are endless possibilities for refactoring. I just chose one of them that seemed to fit my purpose.
 
Actually, a problem with my approach would surface if I didn't have access to the code of VersementObject. In this case, I wouldn't be able to make it implement my abstract class. However, the "classic" solution (IDateObj) would allow me to derive a class, VersementObjectDerived, from VersementObject and have this class implement IDateObj and use it with ObjListManager. The abstract class solution would be no good in this case because C# won't allow multiple inheritance.
GeneralRe: Some thoughts about alternativesmembermscholz25 Mar '08 - 21:03 
Urs Enzler wrote:
1. In VersementObject.Combine I have a parameter of type IDateObj. But what I really want is a VersementObject, hence I need a conversion. I want to get rid of that.
 
Why not use a Generic interface? Okay that would not be super sexy but would do the same job.

 
I had the same idea when I read this article.
 
The final solution seems too smart for real world use.
Most developers will have problems the understand or maintain such code. ATL/WTL are using such patterns and this is one of the reasons many people don't use/understand them.
 
By the way: Java and C# don't have multiple inheritance for very good reasons, C++ rules if you don't use it!
GeneralRe: Some thoughts about alternativesmemberGabriel Rozsa25 Mar '08 - 21:19 
I know the reasons. And it reminds me of the old joke about C++ letting you shoot yourself in the leg AND giving you a gun to do it... Smile | :) Still, I like being able to "shoot myself in the leg" with some of C++'s features, like multiple inheritance, for example.
GeneralAvoid ICloneable - Risky Implementation [modified]memberDaveBlack25 Mar '08 - 7:15 
While this is a great concept for the use of Generics, I would strongly recommend to remove the implementation of ICloneable. According to the "Framework Design Guidelines (Krzysztof Cwalina and Brad Abrams)" and "Effective C# (Bill Wagner)" the implementation of ICloneable should almost always be avoided - details follow below along with references to each Book. It was actually an admitted "goof" by the .NET CLR team (specifically Krzysztof Cwalina - an Architect on the CLR Team) to ship with the ICloneable interface. The entire .NET Framework does not even consume ICloneable as a parameter in any method.
 
There are several problems with implementing ICloneable:
  1. Cloning has 2 potential implementations - shallow copy and deep copy. Thus, it can be implemented differently and is not consistent across different APIs that implement it. You might expect it to do deep copy when it only does a shallow copy or vice-versa. This is especially dangerous because we developers don't enjoy writing documentation to accurately describe what the implementation does Smile | :)
  2.  
  3. Implementing ICloneable in a base class forces all derived classes to implement it *and* implement it in exactly the same way that the base class does (shallow or deep copy).
  4.  
  5. Shallow Copy and Deep Copy do the same thing for Value Types - they create identical copies by using assignment. However, Shallow Copy and Deep Copy work very differently for Reference Types. In Shallow Copy, copies of Reference Types are *references* to the original object. Whereas, Deep Copy creates a new separate instance and recursively copies all member variables which are Reference Types.
  6.  
  7. Because of issue #3 above, mixing Value Types and Reference Types in a class that implements ICloneable causes quite a few inconsistencies in the behavior of the class.
  8.  
  9. Performance problems arise when using ICloneable on a Value Type that contains only built-in types - e.g. "structs", custom value types, etc.. Simple assignment copies the values more efficiently the Clone() since Clone() must box the value types and the caller must then unbox from the cloned boject to extract the value type.
  10.  
  11. Things get even more complicated when you have a value type that contains Reference Type members. Built-in assignment for value types uses a shallow copy which causes the member variable that is a reference type to be *referenced* by both structs - definitely not what was intended. To fix this you would need to Clone() the reference type member variable *and* you would need to know that the implementation of ICloneable for that reference type created Deep Copies. Because of issue #1 above, you are rolling the dice here on which implementation you'll get on your reference type.
 
In summary, here are the recommended Best Practices for working with ICloneable:
  • Don't - no really, avoid it if at all possible
  • Prefer Copy Constructors over ICloneable
  • Never implement ICloneable on Value Types - use assignment instead
  • Especially avoid implementing ICloneable on non-sealed types
  • If you absolutely must implement ICloneable and need to make deep copies of the entire object hierarchy,
    1. Create an abstract Clone() method in the base class and force the derived classes to implement it. However, do not implement ICloneable in the base class.
    2. Then, create a protected copy constructor in the base class
    3. Have all derived classes use the base class copy constructor in the derived implementation of ICloneable.Clone()
    4. Add any additional deep copy implementation of the derived class members in ICloneable.Clone()
    5. Document, document, document the implementation of ICloneable so that any other Developer on your team, or a consumer of your API, knows exactly what they're going to get when using your ICloneable type.
Book References:
Framework Design Guidelines (Krzysztof Cwalina and Brad Abrams) - Chapter 8.4 p221
Effective C# (Bill Wagner) - Chapter 3, Item 27 p163
 
Dave Black - Principal Consultant, Black Box Solutions, Inc.

modified on Wednesday, December 29, 2010 11:47 AM

GeneralRe: Avoid ICloneable - Risky ImplementationmemberGabriel Rozsa25 Mar '08 - 10:22 
True. Actually the problem is MSDN (deliberately?) being vague about IClonable. There is a slight hint that Clone should perform a deep copy since you have MemberwiseClone for a shallow copy. Plus, a "clone" in the non-IT world means a separate individual identical to the original. You'd never imagine cloned cells to share any of their molecules.
 
I personally use IClonable.Clone ONLY for deep copy and ONLY for reference types. I also use the IClonable interface as a flag to mark classes that are capable of deep copying themselves. But I agree completely that more explicit interfaces could and should be used.
GeneralMonadsmemberAlexey Romanov24 Mar '08 - 8:20 
I've tried this solution in the past to get the monad interface from functional languages:
 
interface IMonad<M, T> where M<T> : IMonad<M, T>
 
It doesn't work, unfortunately -- no higher-kind generics in C# yet...
Questioncuriously recurring template pattern?memberMyName IsNot George21 Mar '08 - 10:43 
That's what Coplien named it iirc:
http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern
AnswerRe: curiously recurring template pattern?memberDaTired22 Mar '08 - 4:38 
Thanks for the remark. I didn't know about CRTP pattern. I guess you could say what I did is the C++ Curiously Recurring Template Pattern applied in C#. Smile | :)
GeneralRe: curiously recurring template pattern?memberJASON_S_24 Mar '08 - 21:21 
Well said mate. Wink | ;)
 
Cheers!!
Jason S
I Blog at http://jsbi.blogspot.com

GeneralVery InsightfulmemberRay Gorski21 Mar '08 - 4:13 
Well written, short and to the point. Helps to eliminate some confusion about generics.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 21 Mar 2008
Article Copyright 2008 by Antoniu-Gabriel Rozsa
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid