22

I have a private static generic method I want to call using reflection, but really I want to 'bundle' it inside of another method. C# 7.0 supports local functions so this is definitely possible.

You would say "why don't you just call it directly?" but I'm using it to get the ability to use an object and System.Type in a strongly typed manner so I need to call it dynamically. This code already works if I have it as it's own private static generic method.

private static void HandleResponse(object data, Type asType)
{
    var application = typeof(Program);

    application
        .GetMethod(nameof(UseAs), BindingFlags.Static | BindingFlags.NonPublic)
        .MakeGenericMethod(asType)
        .Invoke(null, new object[] { data });
}

public static void UseAs<T>(T obj)
{
    Console.WriteLine($"Object is now a: {typeof(T)}:");
};

The above code works. If I pass in:

data: new TestObject(),
type: typeof(TestObject)

I'll actually have a TestObject inside UseAs.

So, I wanted to put this all in a single method, like so:

private static void HandleResponse(object data, Type asType)
{
    void useAs<T>(T obj)
    {
        Console.WriteLine($"Object is now a: {typeof(T)}:");
    };

    var application = typeof(Program);

    application
        .GetMethod(nameof(UseAs), BindingFlags.Static | BindingFlags.NonPublic)
        .MakeGenericMethod(asType)
        .Invoke(null, new object[] { data });
}

Unfortunately, the GetMethod code no longer works. I had heard that on compile time the compiler converts any local functions to static methods so I popped down to the immediate window and ran:

application.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)

... And, I actually DO see this response:

{System.Reflection.MethodInfo[3]}
    [0]: {Void Main(System.String[])}
    [1]: {Void HandleResponse(System.Object, System.Type)}
    [2]: {Void <HandleResponse>g__useAs1_0[T](T)}

It's the last method on the list. Does anyone have any idea how you would access a method like that in a reasonable way?

Thank you!


edit:

I can indeed use UseAs as an ordinary private static method. It's just not going to be used anywhere else so I wanted to "package" it all up inside one method.

In addition, this was really supposed to be a question about finding local functions in general and there doesn't seem to be a question about it anywhere else on StackOverflow. I find it hard to believe that at SOME POINT someone won't, at the very least, be curious about to do so.

I was hesitant to provide any code in the first place because I'm just tinkering with an idea, but the actual goal I'm trying to accomplish is secondary to the question altogether.

  • 4
    "I'm using it to get the ability to use an object and System.Type in a strongly typed manner so I need to call it dynamically" - it's not at all clear to me what you mean by that, and it almost certainly affects the answer. You could use a method group conversion to obtain a delegate, if that helps... – Jon Skeet Apr 11 '17 at 14:01
  • Calling a local function with reflection is like looking for trouble. The name isn't "fixed". It changes based on how many other local functions there are in the same class... So if you modify another method you could change the name of the local function you are interested in. – xanatos Apr 11 '17 at 14:06
  • 2
    Invocation via reflection is primarily intended as a mechanism to access the public surface area of a type. Local functions are by definition private implementation details. I would find a different solution to your problem; make the method public. – Eric Lippert Apr 11 '17 at 14:08
  • 3
    I don't understand why `UseAs` being an ordinary private static method doesn't work for you. – InBetween Apr 11 '17 at 14:09
  • @xanatos: Did not know the name wasn't fixed, or deterministic. If that's the case then I WILL have it be a separate private static method. Thank you! – Brent Rittenhouse Apr 11 '17 at 14:24
  • @Jon Skeet, I'm receiving data as an object, and it's type. I'm trying to find a way to use the object in a strongly typed manner without having to cast it. This method actually works. When I invoke UseAs like this I end up with "obj" being the correct type inside that method. Is there a better way to do this if I only have a System.Type? – Brent Rittenhouse Apr 11 '17 at 14:26
  • Again, you could capture it via a delegate... I'll add an example of that as an answer. (Urgh - actually, that'll be painful as well as you'd need the `T`. Hmm.) – Jon Skeet Apr 11 '17 at 14:32
  • 2
    if you need to determine the type at run time, then that is not strongly-typed, is it? – hometoast Apr 11 '17 at 14:35
  • Hometoast, I don't think that's true. – Brent Rittenhouse Apr 11 '17 at 14:41
  • Well it *is* true in that I could call `HandleResponse("foo", typeof(int))` without the compiler complaining. You're certainly losing some compile-time type safety here. Whether you call that "strong typing" or not is a different matter - I try to avoid that term. – Jon Skeet Apr 11 '17 at 14:42
  • Yes, that is definitely true. I'm banking on the assumption the type coming over the wire is actually correct and the end goal was to have a method where we could use the deserialized object AS it's actual type without a bunch of "var foo = object as IFoo; if (foo != null) { ... }" type checks/casts. – Brent Rittenhouse Apr 11 '17 at 14:56

3 Answers3

17

Okay, I've got a solution. But it's really horrible. It involves creating a delegate from your method with a specific type, then using that to find the generic method, then constructing another specific method and invoking it.

So we go from UseAs<int> to UseAs<T> to UseAs<the-type-we-want>.

It could go horribly wrong in many ways, but it works for the very limited sample I've tested:

// DISCLAIMER: THIS CODE IS FAIRLY HACKY, AND MAY WELL FAIL IN WEIRD
// SITUATIONS. USE WITH EXTREME CAUTION AND LOTS OF TESTS!

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        HandleResponse("foo", typeof(string));
    }

    static void HandleResponse(object data, Type type)
    {
        string local = "This was a local variable";
        void UseAs<T>(T obj)
        {
            Console.WriteLine($"Object is now a: {typeof(T)}:");
            // Proof that we're capturing the target too
            Console.WriteLine($"Local was {local}");
        }

        InvokeHelper(UseAs, data, type);
    }

    // This could be in any class you want
    static void InvokeHelper(Action<int> int32Action, object data, Type type)
    {
        // You probably want to validate that it really is a generic method...
        var method = int32Action.Method;
        var genericMethod = method.GetGenericMethodDefinition();
        var concreteMethod = genericMethod.MakeGenericMethod(new[] { type });
        concreteMethod.Invoke(int32Action.Target, new[] { data });
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Wow... That is indeed a bit terrifying, but also pretty ingenious as It _does_ manage to call the local method without dealing with the fact that the name of it is generated and can change any time. Maybe my tinkering _is_ a case that is so rare no one else would have a use for it but I at least wanted to see if it was possible. Much thanks! – Brent Rittenhouse Apr 11 '17 at 14:51
  • @BrentRittenhouse: If the method weren't generic, it wouldn't be tricky. It's the mixture of a local method *and* calling it by reflection *and* it being generic that make it so evil. – Jon Skeet Apr 11 '17 at 14:53
  • @JonSkeet is there a reason you chose "int" instead of more general "object"? – Felix Keil Jul 27 '18 at 06:45
  • 1
    @FelixKeil: It's taking me a while to get back into this at all, but I believe the point is that it's entirely arbitrary. We're not calling an Action at any point; we're only using that as a way of getting at the local method. – Jon Skeet Jul 27 '18 at 11:42
7

Calling a local function with reflection is like looking for trouble. The name isn't "fixed". It changes based on how many other local functions there are in the same class... So if you modify another method you could change the name of the local function you are interested in.

You can take a look at this TryRoslyn.

There are three classes, Class1, Class2 and Class3. They all have a method M that internally has a local function Test. Class1 and Class2 are identical to the last character. The local method is then compiled to a method named <M>g__Test0_0(). Class3 introduces before the M method another method, Filler, with another local function (Foo) that is then compiled to <Filler>g__Foo0_0. In this case the local method of M is named <M>g__Test1_0().

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Would `nameof` mitigate this? – Kenneth K. Apr 11 '17 at 14:33
  • @KennethK. No because the `nameof` will be resolved to `"useAs"`. From my observation, for simple cases, the name is *<(classname)>g__(local method name)(index of the containing method name in the name table of the class)_(index of the local method in the containing method)* – xanatos Apr 11 '17 at 14:38
0

I know this is an old question now - but I'm trying to understand why exactly HandleResponse can't itself be a generic method? You could pass the type on to the local function, which itself can use the same outer type. This eliminates the need for reflection. It means new methods are going to be generated with every call using a different type - but I don't know how you could avoid that step in the compile, and execution of the code - yours included as UseAs is generic. So here's what I'm thinking.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;


[TestClass]
public  class testy
{
    class a { }
    class b : a { }

    [TestMethod]
    public void caller()
    {
        HandleResponse<a>(new b());
    }

    private static void HandleResponse<T>(object data)
    {
        UseAs((T)data);

        void UseAs(T obj)
        {
            System.Diagnostics.Debug.WriteLine($"Object is now a: {typeof(T)}:");
        }
    }
}

I'm under the assumption that you're essentially trying to cast one type to another valid type. Your code wouldn't be type-safe in that case, and could fail with something like "obj is not convertable to type DateTime". My solution is no different. I assume that you can cast your object to a T. I'm not checking if its converible to that type (a valid use of reflection in this case). So I believe my little solution is functionally equivalent.

Simon Miller
  • 890
  • 7
  • 7