views:

517

answers:

2

Thanks to Jon Skeet's answer in this question I have the following working:

public delegate BaseItem GetItemDelegate(Guid itemID);

public static class Lists
{
  public static GetItemDelegate GetItemDelegateForType(Type derivedType)
  {
    MethodInfo method = typeof(Lists).GetMethod("GetItem");
    method = method.MakeGenericMethod(new Type[] { derivedType });
    return (GetItemDelegate)Delegate.CreateDelegate(typeof(GetItemDelegate), method);
  }

  public static T GetItem<T>(Guid itemID) where T : class { // returns an item of type T ... }
}

public class DerivedItem : BaseItem { }

// I can call it like so:
GetItemDelegate getItem = Lists.GetItemDelegateForType(typeof(DerivedItem));
DerivedItem myItem = getItem(someID); // this works great

When I try to apply the same thing to a method with a different return type and overloads (those are the only differences I can come up with), I get an annoying "ArgumentException: Error binding to target method." on the call to CreateDelegate. The below is a working example that gets the error, just copy/paste into a console app.

public delegate IEnumerable<BaseItem> GetListDelegate();

public class BaseItem { }
public class DerivedItem : BaseItem { }

public static class Lists
{
  public static GetListDelegate GetListDelegateForType(Type itemType)
  {
    MethodInfo method = typeof(Lists).GetMethod("GetList", Type.EmptyTypes); // get the overload with no parameters
    method = method.MakeGenericMethod(new Type[] { itemType });
    return (GetListDelegate)Delegate.CreateDelegate(typeof(GetListDelegate), method);
  }

  // this is the one I want a delegate to, hence the Type.EmptyTypes above
  public static IEnumerable<T> GetList<T>() where T : class { return new List<T>(0); }
  // not the one I want a delegate to; included for illustration
  public static IEnumerable<T> GetList<T>(int param) where T : class { return new List<T>(0); }

  public static Type GetItemType()
  { // this could return any type derived from BaseItem
    return typeof(DerivedItem);
  }
}

class Program
{
  static void Main(string[] args)
  {
    Type itemType = Lists.GetItemType();
    GetListDelegate getList = Lists.GetListDelegateForType(itemType);
    IEnumerable<BaseItem> myList = (IEnumerable<BaseItem>)getList();
  }
}

As mentioned above, the only differences I can see are:

  1. Different return type (T works, IEnumerable<T> doesn't) [EDIT: this isn't right, first version uses BaseItem, not T; oops]
  2. Overloads (GetItem has no overloads, GetList has several; I only need the delegate to GetList() with no params

Update1: Sam helped me pinpoint some issues. If the return type of the delegate is generic (e.g. IEnumerable<BaseItem>), it's choking when I try to swap base/derived types around. Is there any way I can declare my GetList method like below? I need to be able to indicate that T inherits from BaseItem, but if I could then it would work fine for me.

public static IEnumerable<BaseItem> GetList<T>() where T : class

The other option would be to "genericize" my delegate declaration. All examples I can find use a generic for the params, not the return type. How do I do this (it throws a compiler error cause T is undefined, and it won't let me use the where constraint):

public delegate IEnumerable<T> GetListDelegate();
+1  A: 

After making some minor modifications to get the second example to compile, I was able to run it and it gets and calls the delegate fine.

using System;
using System.Collections.Generic;
using System.Reflection;

namespace ConsoleApplication1
{
    public delegate IEnumerable<BaseItem> GetListDelegate();

    public class BaseItem { }
    public class DerivedItem : BaseItem { }

    public static class Lists
    {
        public static GetListDelegate GetListDelegateForType(Type derivedType)
        {
            MethodInfo method = typeof(Lists).GetMethod("GetList", Type.EmptyTypes); // get the overload with no parameters
            method = method.MakeGenericMethod(new[] { derivedType });
            return (GetListDelegate)Delegate.CreateDelegate(typeof(GetListDelegate), method); // *** this throws an exception ***
        }

        // this is the one I want a delegate to, hence the Type.EmptyTypes above
        public static IEnumerable<T> GetList<T>() where T : class
        {// returns a collection of T items ... 
            return new T[0];
        }

        // not the one I want a delegate to; included for illustration, maybe my different GetMethod() is my problem?
        public static IEnumerable<T> GetList<T>(int param) where T : class
        { // returns a collection of T items ... 
            return new T[0];
        }
    }

    public class GenericDelegate
    {
        public static void Test()
        {

            // I would call it like so, but first line gets exception, where indicated above
            GetListDelegate getList = Lists.GetListDelegateForType(typeof(BaseItem));
            IEnumerable<BaseItem> myList = getList();
        }
    }
}

I'm not sure how you got your second example to compile though. There appears to be a problem here.

public delegate IEnumerable<BaseItem> GetListDelegate();

GetListDelegate getList = Lists.GetListDelegateForType(typeof(DerivedList));
IEnumerable<DerivedList> myList = getList();

The delegate is declared as returning IEnumerable but then you call it and assign the result to IEnumerable. This isn't supported in C# 3.5. It is in C# 4 but it would require declaring the BaseItem/DerivedList differently to declare covariance (or contravariance, I'm not sure which).

Sam
I extracted all this code from my large system at work and changed the type names and stuff to make a postable sample out of it. I should have made a sample app and played around with it first. Change the DerivedList type to DerivedItem. This was a copy/paste/replace error. I've edit my question to fix this. With that in mind, can you restate your last paragraph, because I'm not following you there.
sliderhouserules
Ah, I didn't look at your code sample good enough (damn scrollbars anyway). The whole point of this way of doing things is I have multiple BaseItem-derived types. I *can't* call GetListDelegateForType with the BaseItem type. It won't give me the right thing at all. I need a delegate replacement for GetList<DerivedItem>().
sliderhouserules
Got it all working, thanks for the help Sam.
sliderhouserules
+1  A: 

I've gotten this working by declaring the delegate as just IEnumerable. This allows it to create the delegate. All that was remaining then was just basic casting. The below changes fix the second code block above.

// declare this as non-generic
public delegate IEnumerable GetListDelegate();

and

// do some cast-fu to get the list into a workable form
List<BaseItem> myList = getList().Cast<BaseItem>().ToList();

I can then do myList.Sort() and all the other stuff I am trying to do in my system at work.

sliderhouserules