List.ForEach传入实例方法组未生效的问题咨询
我最近在Unity开发时碰到了一个费解的问题:同样是用List<T>.ForEach从包含的点扩展包围盒,两种写法一个正常工作,另一个编译完全没问题,但运行后完全达不到预期效果,想请教各位大佬这是怎么回事。
正常工作的写法(显式Lambda表达式)
Bounds ret = new(center, Vector3.zero); endPositions.ForEach(pos => ret.Encapsulate(pos)); // endPositions 是 List<Vector3> 类型 return ret;
编译通过但无效的写法(传入方法组)
Bounds ret = new(center, Vector3.zero); endPositions.ForEach(ret.Encapsulate); return ret;
我原本以为第二种写法和第一种效果完全一致,毕竟都是调用ret实例上的方法。但实际运行后ret完全没变化,还是初始化时的状态。我注意到两者的区别是第二种用了方法组而非明确的Lambda,而Encapsulate有两个重载:
public void Encapsulate(Vector3 point); public void Encapsulate(Bounds bounds);
从List<T>.ForEach(Action<T>)的定义来看,应该只会匹配接受Vector3的第一个重载才对,可为什么就是没效果呢?
问题原因解答
嘿,这个坑其实和值类型的特性直接相关!Unity中的Bounds是值类型(struct),而非引用类型(class),这就是关键所在。
当你使用Lambda表达式pos => ret.Encapsulate(pos)时,每次执行都会直接访问当前的ret变量——每一次Encapsulate调用都是在原变量的实例上进行修改,所以包围盒能正确被扩展。
但当你传入方法组ret.Encapsulate时,编译器会为这个方法组创建一个委托。由于Bounds是值类型,这个委托会捕获**ret当前值的副本**,而不是ret变量本身。后续ForEach循环里调用的Encapsulate都是在这个副本上操作,原变量ret根本没被改动,所以最后返回的还是初始状态的包围盒。
简单总结:值类型的实例方法组传递时,会拷贝实例本身;而Lambda表达式是直接引用原变量,这就是两者行为差异的根本原因。
如果想让方法组的写法生效,你可以把Bounds包装成引用类型,或者继续使用Lambda的写法就可以啦。
备注:内容来源于stack exchange,提问作者Yosef Meller

