奇怪的行为:将对象添加到ObservableCollection中

3

当我将一个对象添加到ObservableCollection中并查找它时,我遇到了奇怪的行为。刚添加完后可以找到,但是用相同的代码再次查找时却找不到了?

public class TestClass {
    public TestClass(string s) {
        Str = s;
    }

    public string Str {
        get;
        set;
    }
}

    private ObservableCollection<TestClass> testCollection = new ObservableCollection<TestClass>();
    private List<string> newValueList = new List<string> { "one", "two", "three" };

private void Test() {
    var tmpList = newValueList.Select(p => new TestClass(p));

    foreach (var v in tmpList) {
        testCollection.Add(v);
        if (testCollection.Contains(v))
            Console.WriteLine("YES");
        else
            Console.WriteLine("NO");
    }

    foreach (var v in tmpList) {
        if (testCollection.Contains(v))
            Console.WriteLine("IN");
        else 
            Console.WriteLine("OUT");
    }
}

运行此代码将得到输出:YES YES YES OUT OUT OUT

当使用.ToList()tmpList进行操作,你将得到预期的结果。


1
您应该在TestClass中实现Equals和GetHashCode以进行此类比较。否则,您只能比较引用。 https://msdn.microsoft.com/zh-cn/library/ms173147(v=vs.80).aspx - Daniel Luberda
3个回答

2
你定义了一个类,运行时不知道如何比较它们。因此,当它们具有相同的引用而不是相同的Str时,它会假定它们两个是相等的。换句话说,当ab是相同地址时,你的TestClass的两个对象(ab)是相等的。如果你想改变这种情况,并使ab在具有相同的Str时相等,你应该重写EqualsGetHashCode方法。 你可以在这里阅读更多信息这里这里
foreach (var v in tmpList) {
    testCollection.Add(v);
    if (testCollection.Contains(v))
        Console.WriteLine("YES");
    else
        Console.WriteLine("NO");
}

你正在将v添加到集合中,并检查它是否在其中,因此它将返回“是”。
第二部分。
foreach (var v in tmpList) {
    if (testCollection.Contains(v))
        Console.WriteLine("IN");
    else 
        Console.WriteLine("OUT");
}

在这里,你正在寻找集合中的v(因为惰性求值在foreach迭代中每次生成新实例,所以并非完全相同的对象引用),因此它将返回“OUT”!

我认为明确提到tmpList是惰性评估的是值得的,因此每次迭代它都会创建TestClass的新实例,这就是为什么引用相等失败的原因。如果在tmpList行中添加了ToList(),则会开始按预期打印"OUT"。 - Kyle
我认为真正的问题是懒惰迭代器创建新对象。我不是在比较变量(v)的地址,而是在比较它们指向的地址(对象的地址)。否则,像这样的代码将导致false,但结果是true:var a=v; var b=v; return a==b; 此外,使用.ToList()使用相同的比较方式可以正常工作。如果我错了,请纠正我。 - Raul Molino

1

Select方法返回一个IEnumerable对象,每次在循环中使用该对象时都会调用GetEnumerator,因此分别为两个循环中的每个元素调用Select方法中的Lambda表达式。

循环1:Select(p => new TestClass(p)) 循环2:Select(p => new TestClass(p))

因此,每当循环使用tmpList时,都会执行select语句,调用Lambda表达式,从而为每个循环创建不同的对象集。

您可以通过在Lambda表达式中创建断点来验证这种行为。

您将看到它被调用了六次而不是三次。


顺便提一下,这种行为与ObservableCollection无关,而与LINQ to objects的Select方法的实现有关。 - Faizan Khan
谢谢,我认为这是最好的解释,真正发生了什么。 - Raul Molino

1
问题在于tmpList不是一个列表,而是一个“懒惰”的迭代器,每次你foreach它时都会创建新的对象。

请更正这行代码:

var tmpList = newValueList.Select(p => new TestClass(p)).ToList();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接