views:

54

answers:

3

Given a structure like this:

class Parent { }

class Child : Parent { }

I have a method that takes a generic type with a constraint that the object is of type Child

static void doSomething<T>() where T : Child
{
    if (typeof(T) == typeof(Parent))
    {
        /* ... */
    }
    else if (typeof(T) == typeof(Child))
    {
        /* ... */
    }
}

the only problem is that, if I have:

class implementsParent : Parent { }

class implementsChild : Child { }

calling the generic method with a type of implementsParent won't work:

doSomething<implementsParent>(); // compile error
doSomething<implementsChild>(); // works fine

I'm trying to work around the fact that overloading a generic method doesn't take into account constraints.

What can I do here?

@Sander Rijken: Additional information.

I need to use the generic type to call an ORM framework method like so:

static void doSomething<T>() where T : Child
{
    if (typeof(T) == typeof(Parent))
    {
        Parent obj = orm.GetObject<T>(criteria);
    }
    else if (typeof(T) == typeof(Child))
    {
        Child obj = orm.GetObject<T>(criteria);
    }
}

having the constraint be where T : Parent causes the Child obj = orm.GetObject() to break because T cannot be converted to Type 'Child'

@Richard Hein:

Originally, I had 2 methods, each with a constraint to one of child/parent (in this case: XPObject and XPCustomObject from the DevExpress ORM - XPObject inherits from XPCustomObject).

The methods look like so:

static void removeBlank<T>(UnitOfWork uow) where T : XPObject
{
    T blank = uow.GetObjectByKey<T>(0):
    if (blank != null)
    {
        blank.Delete();
        uow.CommitChanges();
    }
}

XPCustomObject is used (in this case) to have PKs of type short (instead of the default int on XPObjects). So the only change is in the call to get the object:

static void removeBlankCustomObject<T>(UnitOfWork uow) where T : XPCustomObject
{
    T blank = uow.GetObjectByKey<T>((short)0);    
    if (blank != null)
    {
        blank.Delete();
        uow.CommitChanges();
    }
}

The difference is minimal, so I want to merge the two methods together.

+2  A: 
doSomething<implementsParent>();

This fails because it doesn't meet the type constraint. T isn't derived from Child

Did you mean to declare it:

static void doSomething<T>() where T : Parent

Edit: This will work, given the requirement you added.

static void doSomething<T>() where T : Parent
{
    if (typeof(T) == typeof(Parent))
    {
        T obj = orm.GetObject<T>(criteria);
    }
    else if (typeof(T) == typeof(Child))
    {
        T obj = orm.GetObject<T>(criteria);
    }
}
Sander Rijken
I thought so too at first, but unfortunately that doesn't work (see edit).
SnOrfus
Thanks, see my edit.
Sander Rijken
+1  A: 

Aside from the fact that T doesn't derive from child, I think you'd want to use

if (typeof(T).IsAssignableFrom(typeof(Parent))
  ...

Otherwise it'll only fire for things that are exactly the type Parent, not a derived type. Also, I don't think == is properly overloaded for Type. I think you need to use .Equals()

Note: I may have gotten the IsAssignableFrom backwards.

Hounshell
You can safely use == for comparing types, but you're right that .IsAssignableFrom needs to be checked. You reversed the arguments though, needs to be `typeof(Parent).IsAssignableFrom(typeof(T))` (you want to know if T can be assigned to Parent). I didn't go into this, because the question was about a compilation error.
Sander Rijken
+1  A: 

This part of your code doesn’t make sense:

static void doSomething<T>() where T : Child
                        //   ^^^^^^^^^^^^^^^    This says that T must be a Child,
                        //                      which necessarily means it is not
                        //                      going to be a Parent
{
    if (typeof(T) == typeof(Parent))   //  <--  Therefore, this will never hit
    {

You definitely need to define the constraint as T : Parent if you want to be able to pass in a Parent.

You said that your problem is that you can’t cast the output of orm.GetObject<T> to Child. You are right, you can’t cast it directly — but you can cast it to Parent first and then to Child. I think this should work:

static void doSomething<T>() where T : Parent
{
    if (typeof(T) == typeof(Parent))
    {
        Parent obj = (Parent) orm.GetObject<T>(criteria);
    }
    else if (typeof(T) == typeof(Child))
    {
        Child obj = (Child) (Parent) orm.GetObject<T>(criteria);
    }
}

From the point of view of the compiler, T is like a member of the class hierarchy, a bit like this:

                  ┌──────────┐
                  |  Parent  |
                  └─┬──────┬─┘
                    │      │
                    ↓      ↓
            ┌─────────┐  ┌───────┐
            |  Child  |  |   T   |
            └─────────┘  └───────┘

I think this shows you why you can’t cast directly from T to Child, but you can upcast to Parent (which always succeeds because T is a Parent) and then downcast to Child (which does the runtime check for whether it really is a Child, which of course in your case it will be).

(By the way, I am assuming that you need to use orm.GetObject<T> and you can’t use orm.GetObject<Child>. If you can, that would make it simpler, but maybe you can’t because the criteria depend on T.)

Timwi
+1 for using some good old ASCII formatting. Simple and very useful :)
desigeek
I'll take a look at this now but you're right, I must use GetObject<T> because the ORM uses the type T to know what table to pull from.
SnOrfus