为何扩展方法排序时未修改List?解析异常行为原因
列表扩展方法与静态方法的修改异常原因
代码与测试现象
扩展方法代码
public static void OrderInts (this List<int> ints) { ints= ints.OrderBy(c => c).ToList(); } public static void AddInt(this List<int> ints) { ints.Add(34); } public static void AddIntAndOrder(this List<int> ints) { ints.Add(34); ints = ints.OrderBy(c => c).ToList(); } public static void OrderAndAddInt(this List<int> ints) { ints = ints.OrderBy(c => c).ToList(); ints.Add(34); }
测试结果:
OrderInts未修改整数列表AddInt成功为列表添加元素AddIntAndOrder添加了元素但未对列表排序OrderAndAddInt未修改列表
静态工具类代码
public static void ReorderInts(List<int> ints) { ints = ints.OrderBy(c => c).ToList(); } public static void ReorderIntsByRef(ref List<int> ints) { ints = ints.OrderBy(c => c).ToList(); }
测试结果:
Util.ReorderInts(myVar)未修改列表Util.ReorderIntsByRef(ref myVar)成功修改列表
原因分析
核心在于C#中引用类型的参数传递规则,以及扩展方法的本质:
引用类型默认是值传递
当你把List<int>这类引用类型作为参数传递给方法时,传递的是原引用的副本:- 如果只是通过副本调用对象的成员方法(比如
ints.Add(34)),因为副本和原引用指向内存中同一个列表对象,所以修改会同步到外部。 - 如果给参数重新赋值(比如
ints = ints.OrderBy(...).ToList()),只是让副本指向了新的列表对象,原引用的指向完全没变,外部自然看不到变化。
- 如果只是通过副本调用对象的成员方法(比如
扩展方法的参数本质是普通静态方法参数
扩展方法只是语法糖,编译后和普通静态方法完全一致,参数默认也是值传递。所以OrderInts、AddIntAndOrder、OrderAndAddInt中对ints的重新赋值,都不会影响外部的原列表引用。ref关键字传递的是引用的别名
用ref修饰参数时,传递的不是引用的副本,而是原引用本身的"别名"。此时给参数赋值,会直接修改原引用的指向,让它指向新的排序后的列表,外部变量会同步更新。
对应每个现象的具体解释:
OrderInts:重新赋值ints为新的排序列表,但原外部引用仍指向旧列表,所以无变化。AddInt:通过副本调用Add修改了原列表对象的内容,外部可见。AddIntAndOrder:先通过Add修改了原列表,之后重新赋值ints为新列表,但原外部引用未更新,所以只保留Add的效果。OrderAndAddInt:先让副本指向新的排序列表,再给新列表添加元素,但原外部引用还是旧列表,所以外部看不到任何变化。ReorderInts:和OrderInts逻辑一致,值传递引用副本,赋值不影响外部;ReorderIntsByRef用ref传递原引用,赋值后原引用指向新列表,外部可见变化。
内容的提问来源于stack exchange,提问作者user23305349
相关产品推荐
相关产品推荐

