MethodInfo, CreateDelegate and Generic Methods
-
20-09-2019 - |
Question
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:
- Different return type (
T
works,IEnumerable<T>
doesn't) [EDIT: this isn't right, first version usesBaseItem
, notT
; oops] - Overloads (
GetItem
has no overloads,GetList
has several; I only need the delegate toGetList()
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();
Solution
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.
OTHER TIPS
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).