按照ID从通用列表中删除对象

20

我有一个类似这样的域类:

public class DomainClass
{
  public virtual string name{get;set;}
  public virtual IList<Note> Notes{get;set;}
}

如果我要从 IList<Note> 中移除一个项目,应该怎么做?如果它是一个 List,我就能够这样做,但是由于我正在使用 Nhibernate 作为持久化层,它必须是 IList

理想情况下,我希望在我的领域类中有一个像这样的方法:

public virtual void RemoveNote(int id)
{
   //remove the note from the list here

   List<Note> notes = (List<Note>)Notes

   notes.RemoveAll(delegate (Note note)
   {
       return (note.Id = id)
   });
}

但我无法将 IList 强制转换为 List。有没有更优雅的解决方法?


有趣的问题,你不想强制转换为具体类型,因为你事先不知道它。我猜如果您循环所有元素会起作用,但速度会很慢。我不知道答案,我想知道是否使用LINQ查询选择要删除的节点,然后在运行时使用的每个具体类都将根据列表类型、排序、无序等快速或缓慢地执行linq查询... - Davide Piras
你是否有相同ID的笔记?如果没有,你可能想使用IDictionary<int, Note> - Magnus
@Magnus 不,ID将是唯一的。 - gdp
8个回答

40
你可以筛选出你不想要的项目,并创建一个只包含你想要的项目的新列表:
public virtual void RemoveNote(int id)
{
   //remove the note from the list here

   Notes = Notes.Where(note => note.Id != id).ToList();
}

不错 - 我完全是从另一个角度来看待它的。但这样做会有多高效呢? - gdp
没有太大的区别。无论如何,您都必须遍历整个列表。 - luqui

17

编辑2: 这种方法不需要转换为 List

foreach (var n in Notes.Where(note => note.Id == id).ToArray()) Notes.Remove(n);

或者...

Notes.Remove(Notes.Where(note => note.Id == id).First());
第一个是最好的。
如果没有笔记使用该id,第二个会抛出异常。

编辑:感谢Magnus和rsbarro指出我的错误。


4
在迭代列表时移除其中的元素是行不通的。请使用.ToList()方法。 - Magnus
2
@Magnus是正确的,第一个会抛出InvalidOperationException: Collection was modified; enumeration operation may not execute. - rsbarro
1
这可以简化为以下代码:Notes.Remove(Notes.First(note => note.Id == id)); - Jay Rainey

2
如果您可以更改数据结构,我建议使用字典(Dictionary)。那么您可以使用以下代码:
public class DomainClass
{
  public virtual string name{get;set;}
  public virtual IDictionary<int, Note> Notes {get; set;}

  //Helper property to get the notes in the dictionary
  public IEnumerable<Note> AllNotes
  {
    get
    {
      return notes.Select (n => n.Value);
    }
  }

  public virtual void RemoveNote(int id)
  {
     Notes.Remove(id);
  }

如果ID不唯一,请使用IDictionary<int, IList<Note>>


2
你可以手动编写代码。朴素的实现是 O(n*k),其中 n 是列表中项目的数量,k 是要删除的项目数。如果你只想删除一个项目,它就很快。
但是,如果你想删除许多项目,那么对于许多 IList 实现(包括 List,不知道 NHibernate 的列表行为如何),原生实现变成了 O(n^2),你需要编写更多的代码来获得一个 O(n) 的 RemoveAll 实现。
一种旧答案提供的可能实现:List, not lose the reference 这个实现的技巧是将保留的项移动到列表的开头,时间复杂度为 O(n)。然后它继续删除列表的最后一项(通常是 O(1),因为没有元素需要移动),所以截断总共需要 O(n) 的时间。这意味着整个算法的时间复杂度是 O(n)。

1
请注意,在某些情况下最好避免使用公共虚拟函数,而是使用模板方法模式。
 public void Load(IExecutionContext context) 
 { 
      // Can safely set properties, call methods, add events, etc...
      this.Load(context);            
      // Can safely set properties, call methods, add events, etc. 
 }

 protected virtual void Load(IExecutionContext context) 
 {
 }

我想看到一些避免使用“public virtuals”的理由。 - spender
@spender,通过覆盖虚方法,您可以通过跳过基类.Method()调用来破坏基类行为。这种方法也被称为NVI习语。 - sll
@sll 在重写的方法中调用基本方法可能适合也可能不适合,这取决于基本方法的作用,例如,如果您重写了ToString(),在大多数情况下,在您的重写方法中调用base.ToString()是没有意义的。 - Ben Robinson
@Ben Robinson,我必须承认,你说得完全正确。我应该通过添加“在某些情况下考虑”来修改我的帖子。感谢你的指正! - sll
@Ben Robinson,关于重写ToString()方法,让我们想象一个具有属性Id和Name的PersonBase基类,因此它的ToString()将返回"Id=.. Name...",而所有嵌套类都应该只是连接base.ToString() +自己的ToString()实现。 - sll

0

您可以接收一个要删除的项目数组,然后在循环中从列表中删除它们。 看一下这个示例:

IList<int> list = new List<int> { 1, 2, 3, 4, 5, 1, 3, 5 };

var valuesToRemove = list.Where(i => i == 1).ToArray();

foreach (var item in valuesToRemove)
{
    list.Remove(item);
}

0

只需使用 List.RemoveAt("ID");

或者更好的方法是...

try
{
     List.RemoveAt("ID");
}
catch
{
     Console.WriteLine("Out of bounds.");
}

0
我的解决方案是,首先为要删除的项目创建一个哈希集列表, 然后调用RemoveAll项,其中包含HashSet的id:
HashSet<Guid> vListIdToDelete = new HashSet<Guid>();//TODO fill here
vOtherList.RemoveAll(p => vListIdToDelete.Contains(p.ID));

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