如何正确获取Lambda Action的Target并转换为自定义类?
问题分析与解决方案
这个问题的核心是编译器对Lambda表达式捕获变量的自动包装导致的:
当你在foreach里写Outputs.Add((a) => f(3));时,Lambda捕获了循环变量f(一个Action<int>委托),C#编译器会自动生成一个匿名的显示类(就是你调试时看到的DisplayClass),把被捕获的f作为这个类的私有字段存储起来。此时你添加到Outputs里的Action<int>委托,它的Target指向的是这个编译器生成的显示类实例,而不是原来f的Target(你的MyClass实例)。
调试时能看到f字段是因为调试模式下编译器保留了更多调试信息,而发布模式下编译器会优化这些生成的类和字段名(比如变成<f>5__1这类无意义的名字),所以直接访问f字段在非调试环境下会失效。
解决方案1:直接捕获目标实例(推荐)
既然你最终要获取的是f.Target对应的MyClass实例,不如在循环时直接把这个实例提取出来,让Lambda捕获这个实例而非原委托。这样生成的Lambda的Target就是你的MyClass实例,转换时就不会报错了:
List<Action<int>> Outputs = new List<Action<int>>(); List<Action<int>> Inputs; // 假设已填充 foreach(Action<int> f in Inputs) { // 先从原委托中取出MyClass实例 MyClass target = f.Target as MyClass; if(target != null) { // Lambda直接捕获target,而不是f Outputs.Add(a => target.YourMethod(3)); // 替换成你原来f调用的实际方法 } } foreach(Action<int> del in Outputs) { // 现在del.Target就是MyClass实例,可以直接转换 MyClass mc = (MyClass)del.Target; }
这种方法完全不依赖编译器的内部实现,是最稳定可靠的方案。
解决方案2:通过反射获取原委托(应急方案)
如果因为某些原因无法修改Lambda的捕获逻辑,只能通过反射来读取编译器生成类里的原委托字段。这种方法依赖编译器的实现细节,虽然当前版本的C#编译器都会生成这样的字段,但未来可能有变化,所以只建议作为临时方案:
foreach(Action<int> del in Outputs) { var displayClass = del.Target; if(displayClass == null) continue; // 查找显示类中类型为Action<int>的私有实例字段 var field = displayClass.GetType() .GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic) .FirstOrDefault(f => f.FieldType == typeof(Action<int>)); if(field != null) { // 获取原委托实例 var originalAction = (Action<int>)field.GetValue(displayClass); // 转换原委托的Target到MyClass MyClass mc = originalAction.Target as MyClass; if(mc != null) { // 这里可以使用mc了 } } }
内容的提问来源于stack exchange,提问作者Pikachu620
相关产品推荐
相关产品推荐

