F#中如何向System.Collections.Generic.List添加对象副本而非引用
问题原因
Postcode.PostcodeRange为引用类型(或带可变字段的值类型),你全程仅初始化了一个currentPostCodeRange实例,调用Add时仅将该实例的引用存入列表,后续修改实例属性时,列表中所有指向该实例的元素会同步更新。
解决方案
- 方案1:添加元素时生成副本(改动最小)
如果Postcode.PostcodeRange是F#记录类型,直接使用内置的with表达式生成新实例再添加到列表,仅需修改添加行的代码:
currentPostCodeRange.To <- thisPostCodeString // with无参数时会生成原记录的完整副本 postcodeRanges.Add({ currentPostCodeRange with })
如果Postcode.PostcodeRange是自定义类,手动拷贝所有属性生成新实例即可:
let copyRange = Postcode.PostcodeRange( RangeType = currentPostCodeRange.RangeType, From = currentPostCodeRange.From, To = thisPostCodeString ) postcodeRanges.Add(copyRange)
- 方案2:改用函数式写法彻底规避可变状态问题
F#原生优先推荐不可变值,你可以直接通过序列表达式和折叠操作生成结果,不需要维护可变变量,从根源上避免引用重复问题:
let postcodeRanges = seq { 1 .. 9999 } // 两两组合相邻邮编判断是否属于同一范围 |> Seq.pairwise |> Seq.fold (fun (currentRange, finishedRanges) (prevCode, currCode) -> let nextIsRural = checkIsRural currCode let currIsRural = checkIsRural prevCode if nextIsRural <> currIsRural then // 范围中断,存储已完成的范围,初始化新范围 let completed = { currentRange with To = sprintf "%04d" prevCode } let newRange = { RangeType = if nextIsRural then Postcode.RangeType.Rural else Postcode.RangeType.Normal; From = sprintf "%04d" currCode; To = sprintf "%04d" currCode } (newRange, completed :: finishedRanges) else // 范围延续,更新当前范围的截止值 ({ currentRange with To = sprintf "%04d" currCode }, finishedRanges) ) ({ RangeType = Postcode.RangeType.Normal; From = "0001"; To = "0001" }, []) // 加入最后一个未处理的收尾范围 |> fun (lastRange, ranges) -> lastRange :: ranges |> List.rev // 转换为System.Collections.Generic.List,和你原有代码的类型完全兼容 |> ResizeArray
内容的提问来源于stack exchange,提问作者Martin Thompson
相关产品推荐
相关产品推荐

