【问题标题】:Remove object from generic list by id按 id 从通用列表中删除对象
【发布时间】:2011-06-25 13:56:14
【问题描述】:

我有一个这样的域类:

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

我将如何从IList&lt;Note&gt; 中删除项目?如果它是一个列表,我将能够做到这一点,但它必须是一个 IList,因为我使用 Nhibernate 作为我的持久层。

理想情况下,我希望在我的域类中使用这样的方法:

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 查询。 .
  • 你有相同ID的笔记吗?如果不是,您可能想改用IDictionary&lt;int, Note&gt;
  • @Magnus 不,ID 将是唯一的

标签: c# list generics compare


【解决方案1】:

您可以过滤掉您不想要的项目并创建一个仅包含您想要的项目的新列表:

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

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

【讨论】:

  • 很好——我从完全不同的角度来研究它。不过,这会有多高的效率?
  • 应该没有太大区别。无论如何,您都必须遍历整个列表。
【解决方案2】:

Edit2:此方法不需要转换为 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 指出我的错误。

【讨论】:

  • 在迭代列表时从列表中删除项目,不起作用。做一个.ToList()
  • @Magnus 是对的,第一个会抛出一个InvalidOperationException: Collection was modified; enumeration operation may not execute.
  • 这可以简化为:Notes.Remove(Notes.First(note =&gt; note.Id == id));
【解决方案3】:

如果您可以更改数据结构,我建议您使用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&lt;int, IList&lt;Note&gt;&gt;

【讨论】:

    【解决方案4】:

    您可以手动编码。简单的实现是 O(n*k),其中 n 是列表中的项目数,k 是您要删除的项目数。如果您只想删除单个项目,则速度很快。

    但是如果你想删除许多项目,那么对于许多 IList&lt;T&gt; 实现(包括 List&lt;T&gt;,不知道 NHibernate 的列表的行为方式),本机实现变为 O(n^2),你需要编写更多代码来获得O(n) RemoveAll 实现。

    旧答案的一种可能实现:List, not lose the reference

    这个实现的技巧是在 O(n) 中将保留的项目移动到列表的开头。然后它不断删除列表的最后一项(通常是 O(1),因为不需要移动任何元素),所以截断变为 O(n) 总数。这意味着整个算法是 O(n)。

    【讨论】:

      【解决方案5】:

      请考虑,在某些情况下最好避免公共虚拟,以这样的方式使用template method 模式:

       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,通过覆盖虚拟方法,您可以通过跳过 base.Method() 调用来破坏基类行为。这种方法也被称为 NVI 成语。
      • @sll 在重写的方法中调用基方法可能合适也可能不合适,具体取决于基方法的作用,例如如果你覆盖了 ToString(),在大多数情况下,在你覆盖的方法中调用 base.ToString() 没有任何价值。
      • @Ben Robinson,我必须同意,你完全正确。我应该通过添加“在某些情况下考虑”来修改我的帖子。谢谢你的一点!
      • @Ben Robinson,关于覆盖 ToString(),让图像 PersonBase 基类具有属性 Id 和 Name,因此其 ToString() 将返回“Id=.. Name...”,并且所有嵌套类都应该只连接 base.ToString() + 自己的 ToString() 实现
      【解决方案6】:

      您可以收到一组要删除的项目。比从循环列表中删除它们。 看看这个样本:

      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);
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-08-03
        • 2020-04-17
        • 1970-01-01
        • 2018-11-13
        • 1970-01-01
        • 2014-12-10
        相关资源
        最近更新 更多