Mono.Cecil: Getting Method Reference from delegate passed as Generic Parameter

132 Views Asked by At

I'm trying to get an understanding of which concrete types are providing the implementations of interfaces in an IOC (dependency injection) container. My implementation works fine when there are no delegates involved. However, I'm having trouble when a delegate method is passed as the type factory, as I can't get Mono.Cecil to give me the concrete type or a method reference to the factory back. I'm specifically in this case trying to build a component that can work with the IServiceCollection container for .Net ASP.Net REST APIs. I've created a 'minimised' set of code below to make it easy to explain the problem.

Consider the following C# code:

interface IServiceProvider {}
interface IServiceCollection {}
class ServicesCollection : IServiceCollection {}
interface IMongoDBContext {}

class MongoDBContext : IMongoDBContext
{
     public MongoDBContext(string configName) {}
}

static class Extensions
{
     public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class
     {
          return null;
     }
}

class Foo
{
     void Bar()
     {
          IServiceCollection services = new ServicesCollection();
          services.AddSingleton<IMongoDBContext>(s => new MongoDBContext("mongodbConfig"));
     }
}

When successfully locating the 'services.AddSingleton' as a MethodReference, I'm unable to see any reference to the MongoDBContext class, or its constructor. When printing all the instructions .ToString() I also cannot seem to see anything in the IL - I do see the numbered parameter as !!0, but that doesn't help if I can't resolve it to a type or to the factory method.

Does anyone have any ideas on how to solve this?

1

There are 1 best solutions below

2
Vagaus On

Most likely your code is looking in the wrong place.

C# compiler will try to cache the conversion of lambda expression -> delegate.

if you look in sharplab.io you'll see that the compiler is emitting an inner class '<>c' inside your Foo class and in that class it emits the method '<Bar>b__0_0' that will be passed as the delegate (see opcode ldftn).

I don't think there's an easy, non fragile way to find that method.

That said, one option would be to:

  1. Find the AddSingleton() method call
  2. From there start going back to the previous instructions trying to identify which one is pushing the value consumed in 1 (the safest way to do that would be to consider how each instruction you are visiting changes the stack). In the code I've linked, it would be IL_0021 (a dup) of Bar() method.
  3. From there, do something similar to 2, but now looking for the instruction that pushes the method reference (a ldftn) used by the ctor of Func<T, R>; in the code linked, it would be IL_0016.
  4. Now you can inspect the body (in the code linked, Foo/'<>c'::'<Bar>b__0_0')

Note that this implementation has some holes though; for instance, if you call AddSingleton() with a variable/parameter/field as I've done (services.AddSingleton(_func);) you'll need to chase the initialization of that to find the referenced method.

Interestingly, at some point Cecil project did support flow analysis (https://github.com/mono/cecil-old/tree/master/flowanalysis).

If you have access to the source code, I think it would be easier to use Roslyn to analyze it (instead of analyzing the assembly).