如何在ObservableCollection类中更新单个项目?

49

我该如何更新ObservableCollection类中的单个项?

我知道如何添加。我也知道如何在“for”循环中逐一搜索ObservableCollection中的项(使用Count表示项的数量),但是我如何更改现有项。如果我使用“foreach”找到需要更新的项,那么我如何将其放回ObservableCollection中?


4
你的意思是“移除一个项目并将另一个项目放在它的位置”,还是仅仅“更新项目属性并触发事件以更新我的用户界面”? - Neil Barnwell
@NeilBarnwell,我不是楼主,但当我更改列表中已有项目的属性时,我需要UI进行更新。 - DFSFOT
你只需要每个项目实现IObservable。 - Neil Barnwell
4个回答

53

通常情况下,您不能在迭代中更改集合(使用foreach)。当然,解决这个问题的方法是在修改集合时不进行迭代。(x.Id == myId和LINQ的FirstOrDefault仅为您的条件/搜索占位符,重要的是您已经获得了对象和/或对象的索引)

for (int i = 0; i < theCollection.Count; i++) {
  if (theCollection[i].Id == myId)
    theCollection[i] = newObject;
}
或者
var found = theCollection.FirstOrDefault(x=>x.Id == myId);
int i = theCollection.IndexOf(found);
theCollection[i] = newObject;
或者
var found = theCollection.FirstOrDefault(x=>x.Id == myId);
theCollection.Remove(found);
theCollection.Add(newObject);

或者

var found = theCollection.FirstOrDefault(x=>x.Id == myId);
found.SomeProperty = newValue;
如果最后一个例子可行,而你真正需要知道的是如何使观察ObservableCollection 的事物意识到变化,那么你应该在对象类上实现INotifyPropertyChanged 接口,并确保在更改属性时触发PropertyChanged 事件(理想情况下,如果你拥有接口,它应该在所有公共属性上实现,但从功能上来说,只有你将要更新的属性才真正重要)。

3
实现INotifyPropertyChanged解决了我的更新问题。 - Tadija Bagarić
第二点对我很有趣。我之前尝试了 found = newObject,但是没有起作用。这一定意味着 FirstOrDefault 返回了一个重复的对象实例。你使用 IndexOf 获取真正的实例的解决方案对我很有帮助。 - JKennedy
1
@user1 你误解了拥有对象引用的含义。FirstOrDefault并没有复制实例本身。如果您修改该实例上的属性,则会在所有地方更改它。局部变量found只是一个引用,例如,“该实例位于[内存位置]123”。集合还有一些东西也说“该实例位于123”。如果您将found更改为读取“该实例位于434”,则集合仍然有一些东西说“该实例位于123”。 - Tim S.

50

您无需删除元素,然后进行更改和添加。您可以简单地使用LINQ FirstOrDefault方法,通过适当的谓词查找必要的项并更改其属性,例如:

var item = list.FirstOrDefault(i => i.Name == "John");
if (item != null)
{
    item.LastName = "Smith";
}

ObservableCollection中移除或添加项目将生成CollectionChanged事件。


你能否用两个测试来加倍它,例如 (i => (i.Name == "John") && (i.Street == "Main"))? - xarzu
@xarzu lambda后面的所有内容都被视为布尔值进行评估。LINQ是处理集合的强大方法,值得花些时间学习其中可用的不同方法。 - Lance McCarthy
3
这会更新我的数据集,但不会更新用户界面?我需要做些什么吗? - Jon Koivula
2
@jay_t55 你是说我需要用一个新的替换当前的项目来源?这个新的和现在的完全一样,只是其中一个成员的单个字段改变了吗?乍一看似乎非常浪费。特别是考虑到可观察集合能够检测并自动更新到GUI,当我们添加或删除对象时... - Konrad Viltersten
1
@AhmedMohammed 如果列表项是引用类型,则实际项将被更新。 - Kirill Polishchuk
显示剩余2条评论

11

这是一些基于Collection类的扩展方法示例,参考Tim S's examples:

使用FirstOrDefault的C#示例

public static void ReplaceItem<T>(this Collection<T> col, Func<T, bool> match, T newItem)
{
    var oldItem = col.FirstOrDefault(i => match(i));
    var oldIndex = col.IndexOf(oldItem);
    col[oldIndex] = newItem;
}

使用索引循环的计算机科学

public static void ReplaceItem<T>(this Collection<T> col, Func<T, bool> match, T newItem)
{
    for (int i = 0; i <= col.Count - 1; i++)
    {
        if (match(col[i]))
        {
            col[i] = newItem;
            break;
        }
    }
}

用法

假设你有以下类设置

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

您可以像这样调用以下任一函数/实现,其中match参数用于标识您想要替换的项:
var people = new Collection<Person>
{
    new Person() { Id = 1, Name = "Kyle"},
    new Person() { Id = 2, Name = "Mit"}
};

people.ReplaceItem(x => x.Id == 2, new Person() { Id = 3, Name = "New Person" });

VB with Indexed Loop

<Extension()>
Public Sub ReplaceItem(Of T)(col As Collection(Of T), match As Func(Of T, Boolean), newItem As T)
    For i = 0 To col.Count - 1
        If match(col(i)) Then
            col(i) = newItem
            Exit For
        End If
    Next
End Sub  

使用FirstOrDefault的VB

<Extension()>
Public Sub ReplaceItem(Of T)(col As Collection(Of T), match As Func(Of T, Boolean), newItem As T)
    Dim oldItem = col.FirstOrDefault(Function(i) match(i))
    Dim oldIndex = col.IndexOf(oldItem)
    col(oldIndex) = newItem      
End Sub

4
这取决于对象的类型。
如果它是一个普通的C#类,只需更改对象的属性。您不必对集合做任何事情。集合保存了对该对象的引用,即使对象的属性发生变化,该引用也是相同的。对象上的更改不会触发集合本身的更改通知,因为集合并没有真正改变,只是其中一个对象发生了改变。
如果它是一个不可变的C#类(例如字符串),一个struct或另一个值类型,则必须删除旧值并添加新值。

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