The missing link in C# generics: TThis

by justin 13. May 2009 11:50

I was talking to a friend of mine (Mike Hurley, who doesn’t have a blog so I can’t properly cite him) and he had an interesting solution to a problem in C# that has nagged me for a while. The problem is with strongly typing generic members to that of a sub-class. Let me explain.

The example he gave me was with ICloneable. ICloneable has a single method Clone, which returns an object. Rather than casting everywhere he decided to take the next logical step, which was to make a generic IClonable.

public interface ICloneable<T> : ICloneable
{
    T Clone();
}

Now to follow this logic you might want to create a ClonableBase class that either provides virtual methods to implement the cloning or has a generic scheme (such as serialization / deserialization) to do the cloning automatically for you.

public abstract class CloneableBase<T> : ICloneable<T>
    where T : ICloneable<T>
{
    object ICloneable.Clone()
    {
        return this.Clone();
    }

    public T Clone()
    {
        T clone = default(T);
        // do clone...
        return clone;
    }
}

The only problem with this is that you have to know what T is. In this case, T is the subclass of this. But there is no way to express TThis in C#. The best you can do is to have the recursive generics like above which when implemented would look like this.

public class Example : CloneableBase<Example>
{
}

While this works just fine in practice it has a few drawbacks. The first is just that it sort of makes your brain hurt a little bit and feels a little wrong. Another reason is that it makes performing reflection and casting much more difficult (though I suppose you could always down cast it into IClonable in this example). And finally it doesn’t actually express the constraint the base class is expecting, meaning you can write invalid code. Consider this:

public class Example : CloneableBase<Foo>
{
}

This would build just as easily but is clearly, totally wrong. There is no way in C# to properly express this constraint correctly. Not yet at least. Mike was recommending adding a TThis feature to the language. The corresponding code with this feature might look like this.

public abstract class CloneableBase : ICloneable<TThis>
{
    object ICloneable.Clone()
    {
        return this.Clone();
    }

    public TThis Clone()
    {
        TThis clone = default(TThis);
        // do clone...
        return clone;
    }
}

public class Example : CloneableBase
{
}

Where TThis is literally the type of the instance, or the Type about to be instantiated. I’m not sure how you would retrofit this to the current generics system but it seems plausible. If you’re not sure how common or useful this feature would be take a look at CSLA and see BusinessBase<T> among others.

Tags:

Comments

5/9/2009 9:09:30 PM #

Mike Strobel

Sounds like the actual missing component here is return type variance in method overrides:

public class CloneableBase : ICloneable<CloneableBase>
{
    public virtual CloneableBase Clone() { ... }
}

public class Example : CloneableBase
{
    public override Example Clone() { ... }
}

I'd like to see this as well.

Mike

Mike Strobel |

5/9/2009 11:20:54 PM #

Yes that would be very useful as well.

Justin Chase |

5/10/2009 2:14:33 PM #

Mike Hurley

Well, if the feature was implemented in C# as I was thinking, the syntax would be even cleaner. Essentially, ICloneable would be redeclared as:

public interface ICloneable
{
Tthis Clone();
}

And you wouldn't need to change any of your code since the compiler already had to be smart in the original case and autobox value types that were cloned...unless you wanted remove the casts from object.

The interesting point would be how midlevel class references would work, at least from the perspective of intellisense.

Assume you had:
FooBase : ICloneable
FooA : FooBase
FooAB : FooA

What would the following show in intellisense?
FooAB origObj = new FooAB();

FooAB cloneAB = origObj.Clone(); // "FooAB Clone()" obviously
FooA cloneA = origObj.Clone(); // I'd expect it to show "FooA Clone()"
FooBase cloneBase = origObj.Clone(); // I'd expect it to show "FooBase Clone()"

I don't think it would matter outside of intellisense as it would just be autocasting to base types.

Besides the cloning case, it would also allow typeof(Tthis) in a base class and get the type object of the derived type which I suppose is overkill since we already have GetType().

Mike Hurley |

5/10/2009 3:04:58 PM #

Mike Hurley

Oops, my intellisense examples are bit off.

A better example would be:
FooAB origObj = new FooAB();

FooA a = origObj;
FooA cloneA = a.Clone() // "FooA Clone()"
FooBase bse = origObj;
FooBase cloneBase = bse.Clone() // "FooBase Clone()"

Where of course at this point cloneA and cloneBase are references to a full FooAB object.

Mike Hurley |

5/11/2009 8:53:32 AM #

That's interesting, I wouldn't have thought to put TThis on an interface but that would be more elegant.

Justin Chase |

Comments are closed

About Me

sweetest hat ever

I'm a software developer from Minnesota and this blog largely focuses on various technical concepts I am thinking about at the moment. I currently work for Microsoft in the St. Paul office of the Expression product group.

RecentPosts