如何使用私有类型调用Method<T>(Func<Action<T>> action)方法

10

我希望这段代码能够说明我的问题。我需要像注释掉的那一行一样调用CallEvent方法。 我无法访问ThirdPartyAnotherThirdParty类。
以下是我目前所做的:

public class ThirdParty
{
    private struct MsgType
    { }

    private static void AnotherFunc(MsgType msg)
    { }
}

public class AnotherThirdParty
{
    public static void CallEvent<T>(Func<int, Action<T>> action, T arg)
    { }
}

public class MyClass
{
    public static void Main()
    {
        Type MsgType = typeof(ThirdParty).GetNestedType(
            "MsgType", BindingFlags.Instance | BindingFlags.NonPublic);
        object msg = Activator.CreateInstance(MsgType);

        MethodInfo CallEvent = typeof(AnotherThirdParty).GetMethod("CallEvent");
        CallEvent = CallEvent.MakeGenericMethod(MsgType);

        MethodInfo AnotherFunc = typeof(ThirdParty).GetMethod(
            "AnotherFunc", BindingFlags.Static | BindingFlags.NonPublic);

        CallEvent.Invoke(null, new object[] {???, msg});
        //CallEvent<MsgType>((int x) => new Action<MsgType>(AnotherFunc), msg);
        // I can't get my head around how to solve this (Action<msgtype>)
    }
}

我也尝试过:
CallEvent.Invoke(null, new object[]
    {
        new Func<int, Action<object>>((int x) =>
            new Action<object>((object y) =>
                AnotherFunc.Invoke(null, new object[] { y }))),
        msg
    });

我遇到了以下异常:

System.ArgumentException: 无法将类型为'System.Func2[System.Int32,System.Action1[System.Object]]'的对象转换为类型'System.Func2[System.Int32,System.Action1[ThirdParty+MsgType]]。

我该怎么办?

AnotherThirdParty.CallEvent 除了通过 action 发送出去,还会对 T 参数做任何操作吗? - Clint
是的,它调用另一个方法 RaiseEvent<T1, T2, T3>(T1 arg1, T3 arg3, Func<T1, Action<T2>> action, T2 arg2),其中 T1 : int,T3 : int - timo
工厂函数应该做什么?只是在每个整数上返回方法还是其他什么? - Filip Cordas
@FilipCordas 尝试简化示例,实际应用是一个服务器,通知所有连接的客户端有关某些事件的信息,如果这有所帮助。 - timo
4个回答

8
public class ThirdParty
{
    private struct MsgType { }
    private static void AnotherFunc(MsgType msg)
    {
        // Inserted to demonstrate getting here
        Console.WriteLine($"HEY: {msg}");
    }
}

public class AnotherThirdParty
{
    public static void CallEvent<T>(Func<int, Action<T>> action, T arg)
    {
        // Inserted to demonstrate calling the func and then
        // the action
        action(12)(arg);
    }
}

public static void Main()
{
    var msgTypeType = 
        typeof(ThirdParty).GetNestedType("MsgType", BindingFlags.NonPublic);

    // This is the message type we're passing (presumably you'll do more with it)
    var ourMsgTypeArg = Activator.CreateInstance(msgTypeType);

    // Get the reference to the CallEvent method
    var callEventMethod =
        typeof(AnotherThirdParty).GetMethod("CallEvent", BindingFlags.Public | BindingFlags.Static)
        .MakeGenericMethod(msgTypeType);

    // Get the reference to the AnotherFunc method
    var anotherFunc =
        typeof(ThirdParty).GetMethod("AnotherFunc", BindingFlags.NonPublic | BindingFlags.Static);

    // Build the func to pass along to CallEvent
    var func = CreateFunc(msgTypeType, anotherFunc);

    // Call the CallEvent<MsgType> method.
    callEventMethod.Invoke(null, new object[] {
        func,
        ourMsgTypeArg
    });
}

private static Delegate CreateFunc(Type msgType, MethodInfo anotherFunc)
{
    // The func takes an int
    var intArg = Expression.Parameter(typeof(int));

    // The action takes a msgType
    var msgTypeArg = Expression.Parameter(msgType);

    // Represent the call out to "AnotherFunc"
    var call = Expression.Call(null, anotherFunc, msgTypeArg);

    // Build the action to just make the call to "AnotherFunc"
    var action = Expression.Lambda(call, msgTypeArg);

    // Build the func to just return the action
    var func = Expression.Lambda(action, intArg);

    // Compile the chain and send it out
    return func.Compile();
}

这段代码按照您的要求运行,并打印出以下内容:
HEY: UserQuery+ThirdParty+MsgType

我遇到了同样的问题,方法没有做任何事情。但无论他在做什么,这可能都不是正确的方法。 - Filip Cordas
1
我之前表达不够清晰,我的意思是答案很好。错误的部分在于首先需要这样做。 - Filip Cordas
你需要在 action 中使用额外的 lambda 吗? - NetMage
@timohehnen 这很奇怪,它不应该这样做...你所做的只是调用它。可能是在其中做了一些奇怪的事情,也许你可以更新你的问题/提出另一个问题(并链接它),包括AnotherFunc中的反汇编代码和你得到的异常? - Clint
会在周一做,但那时可能有相当多的代码。 - timo
显示剩余5条评论

2

这似乎可以运行:

    MethodInfo miCreateDelegate = typeof(MethodInfo).GetMethod("CreateDelegate", new[] { typeof(Type), typeof(Object) });
    var ActionType = typeof(Action<>).MakeGenericType(MsgType);
    var lambdabody = Expression.Convert(Expression.Call(Expression.Constant(AnotherFunc), miCreateDelegate, new[] { Expression.Constant(ActionType), Expression.Constant(null) }), ActionType);
    var intparm = Expression.Parameter(typeof(int));
    var lambda = Expression.Lambda(lambdabody, intparm);

    CallEvent.Invoke(null, new object[] {
        lambda.Compile(),
        msg
    });

更完整的回答是我如何生成这个表达式的?我使用LINQPad编译了一个简化的、类似的表达式,用字符串替换了MsgType,然后将其转换为Expression

public static void afunc(string x) { }

Expression<Func<int, Action<string>>> lambda = (int x) => new Action<string>(afunc);

然后我使用了LINQPad的Dump()函数来输出表达式树。

lambda.Dump();

在 MSDN Expression 文档中进行了一些探索后,我找到了正确的静态方法来创建这些部分。我已经知道如何从 LINQPad 的扩展方法中实例化泛型类型,该扩展方法可以动态创建匿名类型以扩展 Dump(),以排除匿名对象中的字段,并且我知道如何从一个扩展方法中创建 lambda 表达式,该扩展方法用于将 LINQ 扩展为具有适当的 SQL 可翻译的 Left 和 Right Join 操作。


这很不错,但它涉及到将chuck强制转换为CreateDelegate,实际上可以不用这样做。(看到其他人也在玩Expression真是太好了:P) - Clint
我增加了更多的解释,但这就是编译器将 lambda 调用转换为 new Action<> 的方式。 - NetMage

1
使用Delegate.CreateDelegate方法构造一个Action<MsgType>对象。使用Expression.Lambda<>构造Func<int,Action<T>>
var actionType = typeof(Action<>).MakeGenericType(MsgType);
var funcType = typeof(Func<,>).MakeGenericType(typeof(int), actionType);
var p1 = Expression.Parameter(typeof(int));
var p2 = Expression.Parameter(actionType);
var delegate = Expression.Constant(Delegate.CreateDelegate(actionType, AnotherFunc), funcType);
var lambda = Expression.Lambda(delegate, p1, p2);
CallEvent.Invoke(null, new object[] {
    lambda.Compile()
,   msg
});

系统.ArgumentException: 无法将类型为 'System.Action1[ThirdParty+MsgTyype]' 的对象转换为类型 'System.Func2[System.Int32,System.Action`1[ThirdParty+MsgTyype]]'。 - timo
缺少 Func<int, Action<object>> 部分。请注意这在 .net standard 1.1 上无法工作。 - Filip Cordas
@timohehnen 这个问题现在应该已经解决了。 - Sergey Kalinichenko

0

这会起作用并且会打印出A,但是函数工厂对我来说是个迷,所以我只返回了创建的委托。而且这与 .net standard 1.1 兼容。

static void Main(string[] args)
        {
             Type MsgType = typeof(ThirdParty).GetNestedType(
                "MsgType", BindingFlags.Instance | BindingFlags.NonPublic);
        object msg = Activator.CreateInstance(MsgType);

        MethodInfo CallEvent = typeof(AnotherThirdParty).GetMethod("CallEvent");
        CallEvent = CallEvent.MakeGenericMethod(MsgType);

        MethodInfo AnotherFunc = typeof(ThirdParty).GetMethod(
            "AnotherFunc", BindingFlags.Static | BindingFlags.NonPublic);

        var actionType = typeof(Action<>).MakeGenericType(MsgType);

        var actionDelegate = AnotherFunc.CreateDelegate(actionType);

        var param = Expression.Parameter(typeof(int));
        var funcDelegate = Expression.Lambda(Expression.Constant(actionDelegate),param).Compile();

        CallEvent.Invoke(null, new []{ funcDelegate, msg });


        Console.ReadLine();

        }

        public class ThirdParty
        {
            private struct MsgType
            { }

            private static void AnotherFunc(MsgType msg)
            {
                Console.WriteLine("A");
            }
        }

        public class AnotherThirdParty
        {
            public static void CallEvent<T>(Func<int, Action<T>> action, T arg)
            {
                action(1)(arg);
            }
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接