简体   繁体   中英

Using reflection to invoke generic method passing lambda expression

I have few classes deriving from abstract class A - A1, A2, A3, etc.

I need to register these classes to BsonClassMap using their own discriminators using code like this:

BsonClassMap.RegisterClassMap<A1>(cm =>
{
    cm.AutoMap();
    cm.SetDiscriminator(discriminator)
});

Since I will have more of such classes in future, I want to use reflection to do that:

foreach (var type in Assembly.GetAssembly(typeof(A))
                             .GetTypes().Where(t => t.IsClass 
                                        && !t.IsAbstract 
                                        && t.IsSubclassOf(typeof(A))))
{
    var discriminator = type.GetField("Discriminator")
                            .GetRawConstantValue()
                            .ToString();

    var method = typeof(BsonClassMap).GetMethods()
                        .FirstOrDefault(m => m.IsGenericMethod 
                                             && m.Name == "RegisterClassMap");
    if (method == null) continue;
    var generic = method.MakeGenericMethod(type);
    generic.Invoke(this, null);
}

How can I pass such lambda expression to this function?

cm =>
{
    cm.AutoMap();
    cm.SetDiscriminator(discriminator)
});

I have found many descriptions of how to call generic methods, but can't get this to work.

There are two parts to this question,

To pass a lambda to this method you simply pass it in an array as the second argument to the Invoke call. generic.Invoke(this, new object[]{foo});

However, the problem now is how do you get a object containing a lambda? This doesn't work var l = () => {...} , so whats the deal?

Well lambdas in c# can have two different types. They can be aa delegate ( Func<...> or Action<..> or they can be an expression Expression<Func<...>>

What it actually ends up being depends on what you assign it to, or rather what the compiler infers it to be. So what you have to write to get an expression is Expression<Func<int>> e = () => 3;

Now you can pass e into your invoke call (provided that the expression types match up of course)

If this code is entirely generic, one solution might be to have a helper method that actually returns the expression you pass in:

public Action<T> GetMyDelegate<T>() where T: A{
  return (T cm) => {
    cm.AutoMap();
    cm.SetDiscriminator(discriminator)
  };
}

Also note that you can initialize a delegate such as Action with a method name:

 public void Method1() {
   var a = new Action(Method2);
 }

 public void Method2()
 {  }

You can also create a delegate from a MethodInfo , say if different subtypes of A have their own init method that you find with reflection: http://msdn.microsoft.com/en-us/library/53cz7sc6.aspx

In addition to the answer posted by @aL3891, I mocked up a simple example of how I would call this like so:

In this situation, SampleClass is the standin for BsonClassMap , and AbstractClass stands in for A , etc

class Program
{
    static void Main(string[] args)
    {
        SampleClass.Foo<int>(param =>
            {

            });

        var discoveredTypes = new List<Type>
        {
            typeof(DerivedOne),
            typeof(DerivedTwo),
            typeof(DerivedThree)
        };

        foreach (var type in discoveredTypes)
        {
            var methodType = typeof(Program).GetMethods().FirstOrDefault(x => x.Name == "CreateMethod").MakeGenericMethod(type);

            var method = methodType.Invoke(null, null);

            var staticMethod = typeof(SampleClass).GetMethods().FirstOrDefault(x => x.Name == "Foo").MakeGenericMethod(type);

            staticMethod.Invoke(null, new object[] { method });
        }

        Console.ReadKey();
    }

    public static Action<T> CreateMethod<T>()
    {
        return new Action<T>((param) =>
            {
                Console.WriteLine("This is being invoked with type: " + typeof(T).Name);
            });
    }
}

public abstract class AbstractClass { }

public class DerivedOne : AbstractClass { }
public class DerivedTwo : AbstractClass { }
public class DerivedThree : AbstractClass { }

public class SampleClass
{
    public static void Foo<TGenericType>(Action<TGenericType> setupMethod)
        where TGenericType : new()
    {
        TGenericType instance = new TGenericType();

        setupMethod(instance);
    }
}

The problem is that you're trying to go from a runtime declaration to a compile time generic, so it's easy enough to just make a factory style method you can invoke generically to construct the delegate for you. The CreateMethod method in Program would be involved in making your lambda expression, and then you could invoke the static method on BsonClassMap that way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM