C# 删除具有相同值但不同标识的列表项是有效的,但为什么呢?

C# Removing List Item with same value but different identity, works, but why?

提问人:Argo 提问时间:9/26/2023 最后编辑:marc_sArgo 更新时间:9/26/2023 访问量:61

问:

我试图制作一个删除一些列表项的代码,但后来我意识到,我的代码不应该工作,但可以工作。

我将在本文档的底部解释为什么我认为我的代码“不应该工作”的原因,但让我先解释一下场景本身。

我的代码的基本概念

在给定路径时,对路径的每个节点执行一些拟合逻辑。

详情如下:

  1. 每个节点都可能具有来自前一个节点的传入方向和/或指向下一个节点的传出方向,除非路径是单节点。
  2. 假定传入/传出方向始终水平或垂直对齐。
    • 换句话说,路径只能向 x 和 y 这 4 个方向移动
  3. 我想在未使用的一侧放置一个盖子。
    • 例如,如果传入方向是左边,传出方向是右边,我想在顶部和底部放置一个上限。

测试不应该工作但有效的代码:

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# 列表 标识 相等

评论

1赞 Klaus Gütter 9/26/2023
对于值类型,默认实现 compares all fields,请参阅文档Equals
1赞 Alexei Levenkov 9/26/2023
旁注:请重新阅读有关发布代码的最小可重现示例指南 - 有太多的代码来演示值类型的行为:应该完全没问题,并且需要更短的代码。此外,确实,包含指向您阅读过的文档的链接是个好主意(大概您已经看到过@KlausGütter链接的文档,但可能误解了某些内容)。int

答:

2赞 ℍ ℍ 9/26/2023 #1

或者,C# 的相等运算符的基本行为是否基于对象的值......?

未定义相等运算符 ()。==Dir

但是使用 Remove() 的方法,这确实执行了成员比较。 这种比较使用反射,因此性能不是很好。List<T>Equals()

您可以考虑自己实现 Equality 或使用 record struct record 代替 struct

0赞 Joel Coehoorn 9/26/2023 #2

让我们按照文档进行操作,从 List.Remove() 开始。我们在“备注”部分有以下摘录:

如果 type 实现 IEquatable 泛型接口,则相等比较器是该接口的 Equals 方法;否则,默认的相等比较器为 Object.Equals。T

我在结构上没有看到任何内容,所以让我们看一下 Object.Equals 方法。文档的“备注”部分现在为我们提供了以下信息:IEquatable<T>Dir

当前实例和 obj 参数之间的比较类型取决于当前实例是引用类型还是值类型。

如果当前实例是值类型,则 Equals(Object) 方法将测试值是否相等。价值相等意味着以下几点:

  • 这两个对象属于同一类型......
  • 这两个对象的 public 和 private 字段的值相等。

作为指示值类型,我们可以看到文档明确显示将比较字段。structList.Remove()


但是,如果 Dir 是引用类型(类)呢?

在这种情况下,对象仍然相等。事实上,由于 list 和 / 变量都是从 中的静态成员分配的,因此它们将被分配为对相同对象的引用。所以现在你是使用引用还是价值平等都无关紧要了......无论哪种方式,比较器都将匹配。incomingoutgoingDir