views:

123

answers:

2

I am just starting to get to grips with generics and am (ab)using them to refactor a fairly complex section of my code (I've only been using c# for a little while but am fairly experienced in other languages).

I have an inheritance structure where my classes extend a base class. In the base class I have most of the functionality implemented. But I want to be able to associate these children classes with instances of their siblings.

Here is a simplification of some of the relevant code:

class ParentClass<T>
    where T : ParentClass<T>, new()
{

    public static T Create()
    {
        return new T();
    }

    private object joinedItem;

    public void Join<TJoinee>(TJoinee item)
        where TJoinee : ParentClass<TJoinee>, new()
    {
        joinedItem = item;
    }

}

class ChildOne : ParentClass<ChildOne>
{
}

class ChildTwo : ParentClass<ChildTwo>
{
}

With this code in place I can do something like:

var a = ChildOne.Create();
a.Join(new ChildTwo());

The problem is that I needed to type joinedItem as object when really I want to type it as ParentClass<Something>. Is it possible to prescribe a more specific type to joinedItem? Or am I just horribly abusing the language and should be taking a completely different approach?

+3  A: 

Could you extract an interface from ParentClass<T> that is not dependent on T? That way joinedItem could be typed to the interface.

That seems to be as far as you can take what it looks like you're trying to do (join ParentClass<T> instances to ParentClass<U> instances).

If there is nothing in that ParentClass<T> interface that is not dependant on T, then it will be difficult to find a more useful interface than object without knowing T.

Phil Nash
Hmmm - I may be able to go the interface route... I will have a look through the actual code and see if there is anywhere that will fall down...
vitch
But even if there is *nothing* in the interface that is independent of T, what you say is still a good idea just for the clarity of intent and readability of the code. Even if it's just a marker interface, the compiler will still verify that you don't make impossible casts (i.e. you can downcast an `object` to a `string`, but you cannot downcast an `IJoinable` to a `string` - `string` doesn't implement `IJoinable`.)
Eamon Nerbonne
Good point Eamon
Phil Nash
+1  A: 

The core issue here is that it is not possible to express the notion that a variable must be of type XYZ<_> - with arbitrary type parameter.

You can express that in parameters, then you just need to add constraints (as you do), but you cannot to that to variables (at least, not without adding the variable type to the list of type parameters, which is likely to be syntactically ugly).

So, if you need to express the notion of XYZ<_> for any type _ you will need to represent that notion explicitly as a type (say IXYZ), and ensure that all XYZ<_> : IXYZ's actually inherit from that. Generally, the most flexible way to do that is via an interface, but an abstract base class would work too.

Unfortunately, type classes in general aren't part of generics ;-).

Eamon Nerbonne
Thanks for the answer. When you say "without adding the variable type to the list of type parameters" do you mean adding it the type parameters at the class level?
vitch
In your case, yes, you'd need to add the type parameter at class level, since you want to specialize a variable of the class -- I don't recommend doing that though, that's likely to make your code less maintainable. What I was speaking of however was just the construct you used whereby the Join method has a generic type parameter TJoinee which is constrained. In any case - when in doubt, use interfaces to specify the behavior you require; that may be much easier and much more flexible (since a class can implement plenty of interfaces).
Eamon Nerbonne
Thanks for the clarification. I went the interfaces route and all seems to be working well!
vitch