提问人:Argo 提问时间:9/26/2023 最后编辑:marc_sArgo 更新时间:9/26/2023 访问量:61
C# 删除具有相同值但不同标识的列表项是有效的,但为什么呢?
C# Removing List Item with same value but different identity, works, but why?
问:
我试图制作一个删除一些列表项的代码,但后来我意识到,我的代码不应该工作,但可以工作。
我将在本文档的底部解释为什么我认为我的代码“不应该工作”的原因,但让我先解释一下场景本身。
我的代码的基本概念
在给定路径时,对路径的每个节点执行一些拟合逻辑。
详情如下:
- 每个节点都可能具有来自前一个节点的传入方向和/或指向下一个节点的传出方向,除非路径是单节点。
- 假定传入/传出方向始终水平或垂直对齐。
- 换句话说,路径只能向 x 和 y 这 4 个方向移动
- 我想在未使用的一侧放置一个盖子。
- 例如,如果传入方向是左边,传出方向是右边,我想在顶部和底部放置一个上限。
测试不应该工作但有效的代码:
using System;
using System.Collections.Generic;
struct Dir //mock-up object
{
public int fooboo; //dummy value to be printed, to distinguish each objects
public Dir(int val) => fooboo = val;
//these are "Dir a"
public static readonly Dir forward = new Dir(1);
public static readonly Dir back = new Dir(2);
public static readonly Dir left = new Dir(3);
public static readonly Dir right = new Dir(4);
public override string ToString() => fooboo.ToString();
}
public class HelloWorld
{
public static void Main(string[] args)
{
//for filter
//these items are "Dir b"
List<Dir> sides = new List<Dir>{ Dir.forward, Dir.back, Dir.left, Dir.right };
//assume that the previous node is at the front of this node, and the next is at the back
//These two are "Dir c"
var incoming = Dir.forward;
var outgoing = Dir.back;
//filter them out from the list, so that only the 'unused sides' are left
sides.Remove(incoming); //in real scenario, it must be decorated with: if(incoming != null)
sides.Remove(outgoing); //the same as above
//testing if the filter really worked
foreach (var side in sides)
Console.Write(side);
}
}
为什么我认为代码不应该起作用?
虽然我从结构中获得了相同的公共成员,但它是“struct”,这意味着我获得了该对象的复制版本,但没有获得引用。
为了便于区分,我们只将 Dir 的原始静态成员称为“Dir a”,复制的版本称为“Dir b”和“Dir c”。我会在注释中以及代码中保留这些名称。
从身份的角度来看,“Dir b”不是“Dir a”,当然也不是“Dir c”,因为它们都是不同的实例。
在这种情况下,难道不应该认为“Dir c”不是“Dir b”吗?List.Remove(T)
或者,C# 的相等运算符的基本行为是否基于对象的值,而不是它们的标识?
答:
或者,C# 的相等运算符的基本行为是否基于对象的值......?
未定义相等运算符 ()。==
Dir
但是使用 Remove() 的方法,这确实执行了成员比较。
这种比较使用反射,因此性能不是很好。List<T>
Equals()
您可以考虑自己实现 Equality 或使用 record struct 或 record
代替 struct
。
让我们按照文档进行操作,从 List.Remove()
开始。我们在“备注”部分有以下摘录:
如果 type 实现 IEquatable 泛型接口,则相等比较器是该接口的 Equals 方法;否则,默认的相等比较器为 Object.Equals。
T
我在结构上没有看到任何内容,所以让我们看一下 Object.Equals
方法。文档的“备注”部分现在为我们提供了以下信息:IEquatable<T>
Dir
当前实例和 obj 参数之间的比较类型取决于当前实例是引用类型还是值类型。
如果当前实例是值类型,则 Equals(Object) 方法将测试值是否相等。价值相等意味着以下几点:
- 这两个对象属于同一类型......
- 这两个对象的 public 和 private 字段的值相等。
作为指示值类型,我们可以看到文档明确显示将比较字段。struct
List.Remove()
但是,如果 Dir
是引用类型(类)呢?
在这种情况下,对象仍然相等。事实上,由于 list 和 / 变量都是从 中的静态成员分配的,因此它们将被分配为对相同对象的引用。所以现在你是使用引用还是价值平等都无关紧要了......无论哪种方式,比较器都将匹配。incoming
outgoing
Dir
评论
Equals
int