【问题标题】:Anonymous Type Member Equality匿名类型成员平等
【发布时间】:2020-07-31 19:34:27
【问题描述】:

我相信下面的代码应该生成相同匿名类型的两个实例,具有相同顺序的属性,具有相同的名称、类型和值。

    static void Main(string[] args)
    {
        var letterFreq1 = CountLetters("aabbbc");  
        var letterFreq2 = CountLetters("aabbbc");  

        if (letterFreq1.Equals(letterFreq2))
            Console.WriteLine("Is anagram");
        else
            Console.WriteLine("Is not an anagram");
        
    }
    public static object CountLetters(string input) => input.ToCharArray()
                                                            .GroupBy(x => x)
                                                            .Select(x => new {Letter = x.Key, Count = x.Count()})
                                                            .OrderBy(x => x.Letter)
                                                            .ToList();

根据 MS 文档:

如果程序集中的两个或多个匿名对象初始值设定项指定了一系列具有相同顺序且具有相同名称和类型的属性,编译器会将这些对象视为相同类型的实例。它们共享相同的编译器生成的类型信息。

因为匿名类型的 Equals 和 GetHashCode 方法是根据属性的 Equals 和 GetHashCode 方法定义的,所以相同匿名类型的两个实例只有在它们的所有属性都相等时才相等。

我将此解释为我应该在 letterFreq1 和 letterFreq2 上获得相等,但这并没有发生。谁能确定平等检查失败的原因?我试图避免使用手动方法来比较属性值。

这是similar question,但无法帮助解决我的问题。

非常感谢。

【问题讨论】:

  • 你是对的,匿名类型对象将比较相等,但这不是你在做什么。您在 List<T> 上调用 .Equals,它不比较内容,而是进行引用比较,因此即使两个列表具有相同的内容,它们的比较也是不同的。
  • 感谢 Lasse,非常感谢。我没发现那个。
  • 查看我的答案以获得一些替代方案。
  • 您引用的规范部分不适用于此处。它是关于两个或多个单独的表达式,但您只有一个表达式(执行多次)。

标签: c# equality anonymous-types


【解决方案1】:

我相信下面的代码应该生成两个相同匿名类型的实例

不,它会生成两个 List<T> 实例,内容相同。

所以当你执行这个时:

if (letterFreq1.Equals(letterFreq2))

您正在对List<T> 对象调用.Equals 方法,该方法不会覆盖从System.Object 继承的方法,因此会进行引用比较。

不过,您是对的,匿名类型比较相等,并且两个列表实际上具有相同的内容,但列表对象本身并不进行内容比较,因此它们会比较不同。

如果你要哄编译器将两者转换为相同类型的集合,例如:

var letterFreq1 = CountLetters("aabbbc") as IEnumerable<object>;
var letterFreq2 = CountLetters("aabbbc") as IEnumerable<object>;

然后你可以比较它们的内容:

if (letterFreq1.SequenceEqual(letterFreq2))

但是您首先需要知道它们是集合,因此取决于您的代码应该是多么通用/通用,这可能是也可能不是您的解决方案。


然而,我真正的建议是在这种情况下避免使用匿名类型。它们在与局部变量一起使用时很好,但正如您所注意到的,当它们脱离方法的限制时,使用起来会变得非常麻烦。

更好的替换是元组:

void Main(string[] args)
{
    var letterFreq1 = CountLetters("aabbbc");  
    var letterFreq2 = CountLetters("aabbbc");  

    if (letterFreq1.SequenceEqual(letterFreq2))
        Console.WriteLine("Is anagram");
    else
        Console.WriteLine("Is not an anagram");
}

public static List<(char Letter, int Count)> CountLetters(string input)
    => input.ToCharArray()
        .GroupBy(x => x)
        .Select(x => (Letter: x.Key, Count : x.Count()))
        .OrderBy(x => x.Letter)
        .ToList();

更好的解决方案是为此创建一个命名类型,但同样,根据您的情况,这对您来说可能不是一个好的解决方案。

【讨论】:

  • 为什么要返回 List? IEnumerable 在这里可以正常工作,不需要调用 ToList。改变结果集合没有意义,因为它不再对应于输入字符串。
  • 它会工作得很好。只是这是我的编程风格。使输出类型尽可能开放,而不会使它们更难使用。返回List&lt;T&gt; 在我看来 表明该列表是您的,随心所欲。您可以轻松地将其填充到 IEnumerable&lt;T&gt; 类型的变量中,但如果您打算将其填充到列表中,那么您必须在对象上调用 .ToList当它已经是列表时 i> 使它变得更加复杂。但这只是我。 IEnumerable&lt;T&gt; 也可以。
  • 如果您要问为什么方法中有.ToList(),请询问OP 的那个问题。我只是镜像了整个方法,用一个元组替换了匿名对象。
  • 在通过 Linq lambda 使用 EF 物化查询时,由于习惯的力量,我返回了 List。删除 ToList() 并通过 IEnumerable 上的 SequenceEqual 进行比较效果很好。感谢您的全面回答和@asawyer 的评论。
  • @LasseV.Karlsen 很有趣。我尽我所能做完全相反的事情 - 默认情况下密封类型,尽可能使用最少派生类型或接口等。请参阅:stackoverflow.com/a/8717782/426894 更多想法。 signals that the list is yours 但这不是它发出的信号,它表示返回类型是一个列表,仅此而已。问题是,在给定的上下文中,哪种返回类型最适合实现语义?如果需要添加或删除项目,请使用 ICollection,如果他们还需要按索引 IList,如果他们只需要枚举一个集合,则使用 IEnumerable 等。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-25
  • 2019-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-06
  • 1970-01-01
相关资源
最近更新 更多