0

The question is somewhat related to this: How can I cast a delegate that takes a derived-type argument to a delegate with a base-type argument? but I have a dynamic situation.

So lets say I have two classes:

class Base
{ }

class Derived : Base
{ }

static class Workers
{
    public static void DoSomething(Derived obj) { ... }
}

As you can see Workers.DoSomething is Action<Derived> and I want to cast it to Action<Base>. I know this is unsafe but my case is as follows: I keep a dictionary

Dictionary<Type, Action<Base>> actions;

and based on given objects obj.GetType() I retrieve one action and call it. And so I guarantee in my code that such action will be called with an appropriate type.

But those actions depend on the derived type obviously. Now the linked question suggests something like

actions[typeof(Derived)] = (obj) => Workers.DoSomething((Derived)obj);

This is ok in a situation when you know types at compile time. But in my case I retrieve them via reflection. So here's the setup

Type objType;  // given
MethodInfo doSomethingMethod;  // given, guaranteed to be Action<objType>
actions[objType] = // here what?

So far, surprisingly, the simplest solution I came up with is to create the method dynamically like follows:

Type objType;  // given
MethodInfo doSomethingMethod;  // given
var dynamicMethod = new DynamicMethod(
    $"Dynamic{doSomethingMethod.Name}",
    typeof(void),
    new Type[] { typeof(Base) },
    typeof(Base).Module
);
var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Callvirt, doSomethingMethod, null);
il.Emit(OpCodes.Ret);
actions[objType] = (Action<Base>)dynamicMethod
    .CreateDelegate(typeof(Action<Base>));

And so I force the call at CIL level. My real code is slightly more complicated since those actions accept two parameters. But that's just noise.

This works (and there's no cast as a bonus). But it looks kind of... I don't know, unsafe. And probably hard to maintain. Is there a better way to solve my problem?

Note: I want to avoid doSomethingMethod.Invoke due to its significant overhead.

Note 2: I have no control over those classes and actions. I can only inspect them.

freakish
  • 54,167
  • 9
  • 132
  • 169
  • It doesn't look that bad. You can hide it behind a factory, cache per the two. It's not *unsafe*, rather *slightly advanced*. – Wiktor Zychla Sep 20 '19 at 13:07

2 Answers2

1

You seem aware that you are turning the rules of covariance and contravariance upside-down, nevertheless here's something fairly tidy that may work for the situation you describe (you can also check that (b as Derived) != null just to be sure):

  class Base { }

  class Derived : Base { }

  static class Workers 
  { 
    public static void DoSomething(Derived obj) { Console.WriteLine("Test"); } 
  }

  class Program
  {
    static Dictionary<Type, Action<Base>> actions;

    //  *** Note use of dummy to avoid having to know T at compile time and T : Base constraint
    //  (compiler can't infer T from Action<T> alone, this way runtime works out T from given object instance)...
    static void AddAction<T>(T dummy, Action<T> a) where T : Base 
    {
      actions.Add(typeof(T), b => a(b as T));
    }

    static void Main(string[] args)
    {
      actions = new Dictionary<Type, Action<Base>>();
      var o = new Derived();  //  the object you get "from elsewhere"
      AddAction(o, Workers.DoSomething);
      actions[o.GetType()](o);
      Console.ReadKey();
    }
  }

Hope this is useful. (Very curious about the "Why?" though ;-)

AlanK
  • 1,827
  • 13
  • 16
  • That won't do. That's just hiding the fact that the type is known at compile time due to type inference. See what happens when you cast "o" to Base before calling AddAction. And that's my situation. I have Base, I know it is some Derived (since Base in my case is abstract), but I dont have that knowledge at compile time. – freakish Sep 20 '19 at 20:18
  • The reason I do this is this: I have Base and Derived and I have lots of different Visitors which are just classes with methods that act on Derived types. In that Visitor I typically write a main Process(Base node) function which then based on type runs an appropriate function. But the number of Visitor classes keeps getting bigger and bigger. So I wanted to automate this Process(Base node) function at least. Cause its lots of boilerplate. – freakish Sep 20 '19 at 20:30
  • Also note that Base and Derived are not fixed (there may be multiple such inheritance trees) and I can't really modify them to make some simple virtuality. What I'm writing is supposed to be a framework for orhers. – freakish Sep 20 '19 at 20:34
  • I get it. Will think on it some more. – AlanK Sep 21 '19 at 06:32
0

I can't claim this as my idea, but found it here: https://stackoverflow.com/a/32702091/1848953 while researching your question. First:

private static Action<object> ConvertDelegateToAction<T>(Delegate d) { return obj => ((Action<T>)d)((T)obj); }
private static readonly MethodInfo CastMethodInfo = typeof(Program).GetMethod(nameof(ConvertDelegateToAction), BindingFlags.Static | BindingFlags.NonPublic);

used:

public static Action<object> GetActionT(Type t, Delegate d) { return (Action<object>)CastMethodInfo.MakeGenericMethod(t).Invoke(null, new object[] { d }); }

Quite tidy I think. You GetType() at runtime and use Invoke() but only to get the Action<object> up-front. (Instead of a base class, I used an empty interface. Seemed to work fine.)

Let us know if this gives satisfaction.

AlanK
  • 1,827
  • 13
  • 16