【问题标题】:Generic List - moving an item within the list通用列表 - 在列表中移动项目
【发布时间】:2009-01-16 12:08:38
【问题描述】:

所以我有一个通用列表,以及一个 oldIndex 和一个 newIndex 值。

我想尽可能简单地将oldIndex 的项目移动到newIndex...。

有什么建议吗?

注意

该项目应该在(newIndex - 1)newIndex 的项目之间结束它被删除。

【问题讨论】:

  • 您应该更改您勾选的答案。带有newIndex-- 的那个不会导致你说你想要的行为。
  • @Miral - 你认为哪个答案应该被接受?
  • 杰皮尔森的。它会导致在移动之前位于 oldIndex 的对象在移动之后位于 newIndex。这是最不令人惊讶的行为(这是我在编写一些拖放重新排序代码时所需要的)。当然,他说的是ObservableCollection,而不是通用的List<T>,但简单地交换方法调用以获得相同的结果是微不足道的。
  • [newIndex - 1][newIndex] 的项目之间移动项目的请求(和correctly implemented in this answer)行为是不可逆的。 Move(1, 3); Move(3, 1); 不会将列表返回到初始状态。同时ObservableCollectionmentioned in this answer提供了不同的行为,即invertible

标签: c# .net generics list


【解决方案1】:

我知道你说的是“通用列表”,但你没有指定你需要使用 List(T) 类,所以这里是一个不同的镜头。

ObservableCollection(T) 类有一个 Move method,它完全符合您的要求。

public void Move(int oldIndex, int newIndex)

在它下面基本上是这样实现的。

T item = base[oldIndex];
base.RemoveItem(oldIndex);
base.InsertItem(newIndex, item);

所以你可以看到其他人建议的交换方法本质上是 ObservableCollection 在它自己的 Move 方法中所做的。

2015-12-30 更新:您现在可以在 corefx 中查看 MoveMoveItem 方法的源代码,而无需使用 Reflector/ILSpy,因为 .NET 是开源的。

【讨论】:

  • 我想知道为什么 List 上也没有实现这一点,有人能对此有所了解吗?
  • 泛型列表和 List(T) 类有什么区别?我以为他们是一样的:(
  • “通用列表”可以表示任何类型的列表或集合,例如 .NET 中的数据结构,其中可能包括 ObservableCollection(T) 或其他可以实现 listy 接口的类例如 IList/ICollection/IEnumerable。
  • 有人可以解释一下,为什么确实没有目标索引移位(如果它大于源索引)?
  • @Jason 不就是下面的链表吗?当您删除和插入时,您不会移动数据。我在阵营中认为微软从来没有完成任何事情......他们已经足够接近并转向新产品。看看他们所有的企业解决方案。这是一团糟。
【解决方案2】:
var item = list[oldIndex];

list.RemoveAt(oldIndex);

if (newIndex > oldIndex) newIndex--; 
// the actual index could have shifted due to the removal

list.Insert(newIndex, item);

将它们看起来像的扩展方法放入:

    public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
    {
        var item = list[oldIndex];

        list.RemoveAt(oldIndex);

        if (newIndex > oldIndex) newIndex--;
        // the actual index could have shifted due to the removal

        list.Insert(newIndex, item);
    }

    public static void Move<T>(this List<T> list, T item, int newIndex)
    {
        if (item != null)
        {
            var oldIndex = list.IndexOf(item);
            if (oldIndex > -1)
            {
                list.RemoveAt(oldIndex);

                if (newIndex > oldIndex) newIndex--;
                // the actual index could have shifted due to the removal

                list.Insert(newIndex, item);
            }
        }

    }

【讨论】:

  • 如果列表中有两个 item 副本,其中一个出现在 oldIndex 之前,您的解决方案就会崩溃。你应该使用 RemoveAt 来确保你得到正确的。
  • @Garry,很抱歉删除您对我的其他答案的评论,但由于我们不关心 newIndex 上的项目在移动之前的位置,在这种情况下,移动同样好作为交换项目。
  • @GarryShutler 如果我们删除然后插入单个项目,我看不出索引会如何变化。减少 newIndex 实际上会破坏我的测试(请参阅下面的答案)。
  • 注意:如果线程安全很重要,这都应该在lock 语句中。
  • 我不会使用它,因为它令人困惑有几个原因。在列表上定义方法 Move(oldIndex,newIndex) 并调用 Move(15,25) 然后 Move(25,15) 不是身份,而是交换。此外 Move(15,25) 使项目移动到索引 24 而不是我期望的 25。除了交换可以通过 temp=item[oldindex];项目[旧索引]=项目[新索引];项目[新索引]=临时;这在大型阵列上似乎更有效。 Move(0,0) 和 Move(0,1) 也是一样的,这也很奇怪。而且 Move(0, Count -1) 不会将项目移动到末尾。
【解决方案3】:

我知道这个问题很老,但我将 JavaScript 代码的 THIS 响应改编为 C#。希望对你有帮助

public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
{
    // exit if positions are equal or outside array
    if ((oldIndex == newIndex) || (0 > oldIndex) || (oldIndex >= list.Count) || (0 > newIndex) ||
        (newIndex >= list.Count)) return;
    // local variables
    var i = 0;
    T tmp = list[oldIndex];
    // move element down and shift other elements up
    if (oldIndex < newIndex)
    {
        for (i = oldIndex; i < newIndex; i++)
        {
            list[i] = list[i + 1];
        }
    }
        // move element up and shift other elements down
    else
    {
        for (i = oldIndex; i > newIndex; i--)
        {
            list[i] = list[i - 1];
        }
    }
    // put element from position 1 to destination
    list[newIndex] = tmp;
}

【讨论】:

    【解决方案4】:

    List.Remove() 和 List.RemoveAt() 不返回要删除的项目。

    因此你必须使用这个:

    var item = list[oldIndex];
    list.RemoveAt(oldIndex);
    list.Insert(newIndex, item);
    

    【讨论】:

      【解决方案5】:

      我创建了一个用于在列表中移动项目的扩展方法。

      如果我们正在移动 现有 项,则索引不应移动,因为我们正在将一项移动到列表中的 现有 索引位置。

      @Oliver 在下面提到的边缘情况(将项目移动到列表末尾)实际上会导致测试失败,但这是设计使然。要在列表末尾插入一个 new 项,我们只需调用 List&lt;T&gt;.Addlist.Move(predicate, list.Count) 应该失败,因为这个索引位置在移动之前不存在。

      无论如何,我已经创建了两个额外的扩展方法,MoveToEndMoveToBeginning,其源代码可以在 here 找到。

      /// <summary>
      /// Extension methods for <see cref="System.Collections.Generic.List{T}"/>
      /// </summary>
      public static class ListExtensions
      {
          /// <summary>
          /// Moves the item matching the <paramref name="itemSelector"/> to the <paramref name="newIndex"/> in a list.
          /// </summary>
          public static void Move<T>(this List<T> list, Predicate<T> itemSelector, int newIndex)
          {
              Ensure.Argument.NotNull(list, "list");
              Ensure.Argument.NotNull(itemSelector, "itemSelector");
              Ensure.Argument.Is(newIndex >= 0, "New index must be greater than or equal to zero.");
      
              var currentIndex = list.FindIndex(itemSelector);
              Ensure.That<ArgumentException>(currentIndex >= 0, "No item was found that matches the specified selector.");
      
              // Copy the current item
              var item = list[currentIndex];
      
              // Remove the item
              list.RemoveAt(currentIndex);
      
              // Finally add the item at the new index
              list.Insert(newIndex, item);
          }
      }
      
      [Subject(typeof(ListExtensions), "Move")]
      public class List_Move
      {
          static List<int> list;
      
          public class When_no_matching_item_is_found
          {
              static Exception exception;
      
              Establish ctx = () => {
                  list = new List<int>();
              };
      
              Because of = ()
                  => exception = Catch.Exception(() => list.Move(x => x == 10, 10));
      
              It Should_throw_an_exception = ()
                  => exception.ShouldBeOfType<ArgumentException>();
          }
      
          public class When_new_index_is_higher
          {
              Establish ctx = () => {
                  list = new List<int> { 1, 2, 3, 4, 5 };
              };
      
              Because of = ()
                  => list.Move(x => x == 3, 4); // move 3 to end of list (index 4)
      
              It Should_be_moved_to_the_specified_index = () =>
                  {
                      list[0].ShouldEqual(1);
                      list[1].ShouldEqual(2);
                      list[2].ShouldEqual(4);
                      list[3].ShouldEqual(5);
                      list[4].ShouldEqual(3);
                  };
          }
      
          public class When_new_index_is_lower
          {
              Establish ctx = () => {
                  list = new List<int> { 1, 2, 3, 4, 5 };
              };
      
              Because of = ()
                  => list.Move(x => x == 4, 0); // move 4 to beginning of list (index 0)
      
              It Should_be_moved_to_the_specified_index = () =>
              {
                  list[0].ShouldEqual(4);
                  list[1].ShouldEqual(1);
                  list[2].ShouldEqual(2);
                  list[3].ShouldEqual(3);
                  list[4].ShouldEqual(5);
              };
          }
      }
      

      【讨论】:

      • Ensure.Argumentdefined 在哪里?
      • 在普通的List&lt;T&gt; 中,您可以调用Insert(list.Count, element) 在列表末尾放置一些东西。所以你的When_new_index_is_higher 应该调用list.Move(x =&gt; x == 3, 5),这实际上是失败的。
      • @Oliver 在普通的List&lt;T&gt; 中我只需调用.Addnew 项插入到列表的末尾。当移动单个项目时,我们从不增加索引的原始大小,因为我们只是删除单个项目并重新插入它。如果您单击我的答案中的链接,您将找到Ensure.Argument 的代码。
      • 您的解决方案期望目标索引是一个位置,而不是两个元素之间。虽然这适用于某些用例,但不适用于其他用例。此外,您的移动不支持移动到最后(如 Oliver 所述),但您的代码中没有任何地方指示此约束。这也是违反直觉的,如果我有一个包含 20 个元素的列表并且想要将元素 10 移动到最后,我希望 Move 方法来处理这个问题,而不是必须找到保存对象引用,从列表中删除对象并添加对象。
      • @Trisped 实际上,如果您阅读了我的答案,则支持将项目移动到列表的末尾/开头 。您可以查看规格here。是的,我的代码期望索引是列表中的有效(现有)位置。我们正在移动项,而不是插入它们。
      【解决方案6】:

      将当前位于oldIndex 的项目插入newIndex,然后删除原始实例。

      list.Insert(newIndex, list[oldIndex]);
      if (newIndex <= oldIndex) ++oldIndex;
      list.RemoveAt(oldIndex);
      

      您必须考虑到要删除的项目的索引可能会因插入而发生变化。

      【讨论】:

      • 您应该在插入之前删除...您的订单可能会导致列表进行分配。
      【解决方案7】:

      我希望:

      // Makes sure item is at newIndex after the operation
      T item = list[oldIndex];
      list.RemoveAt(oldIndex);
      list.Insert(newIndex, item);
      

      ... 或:

      // Makes sure relative ordering of newIndex is preserved after the operation, 
      // meaning that the item may actually be inserted at newIndex - 1 
      T item = list[oldIndex];
      list.RemoveAt(oldIndex);
      newIndex = (newIndex > oldIndex ? newIndex - 1, newIndex)
      list.Insert(newIndex, item);
      

      ... 可以解决问题,但我在这台机器上没有 VS 可以检查。

      【讨论】:

      • @GarryShutler 视情况而定。如果您的界面允许用户通过索引指定列表中的位置,当他们告诉项目 15 移动到 20 时,他们会感到困惑,而是移动到 19。如果您的界面允许用户在其他项目之间拖动项目如果在列表中,则减少 newIndex 如果它在 oldIndex 之后是有意义的。
      【解决方案8】:

      最简单的方法:

      list[newIndex] = list[oldIndex];
      list.RemoveAt(oldIndex);
      

      编辑

      问题不是很清楚......由于我们不关心list[newIndex] 项目的位置,我认为最简单的方法如下(有或没有扩展方法):

          public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
          {
              T aux = list[newIndex];
              list[newIndex] = list[oldIndex];
              list[oldIndex] = aux;
          }
      

      此解决方案是最快的,因为它不涉及列表插入/删除。

      【讨论】:

      • 这将覆盖newIndex处的项目,而不是插入。
      • @Garry 最终结果会不会一样?
      • 不,你最终会丢失 newIndex 的值,如果你插入就不会发生这种情况。
      【解决方案9】:

      更简单的人就这样做

          public void MoveUp(object item,List Concepts){
      
              int ind = Concepts.IndexOf(item.ToString());
      
              if (ind != 0)
              {
                  Concepts.RemoveAt(ind);
                  Concepts.Insert(ind-1,item.ToString());
                  obtenernombres();
                  NotifyPropertyChanged("Concepts");
              }}
      

      对 MoveDown 执行相同操作,但更改 "if (ind !=Concepts.Count())" 的 if 和 Concepts.Insert(ind+1,item.ToString());

      【讨论】:

        【解决方案10】:

        这就是我实现移动元素扩展方法的方式。它可以很好地处理元素的前后移动和极端移动。

        public static void MoveElement<T>(this IList<T> list, int fromIndex, int toIndex)
        {
          if (!fromIndex.InRange(0, list.Count - 1))
          {
            throw new ArgumentException("From index is invalid");
          }
          if (!toIndex.InRange(0, list.Count - 1))
          {
            throw new ArgumentException("To index is invalid");
          }
        
          if (fromIndex == toIndex) return;
        
          var element = list[fromIndex];
        
          if (fromIndex > toIndex)
          {
            list.RemoveAt(fromIndex);
            list.Insert(toIndex, element);
          }
          else
          {
            list.Insert(toIndex + 1, element);
            list.RemoveAt(fromIndex);
          }
        }
        

        【讨论】:

        • 这是来自弗朗西斯科的答案的副本。
        猜你喜欢
        • 1970-01-01
        • 2013-10-27
        • 1970-01-01
        • 1970-01-01
        • 2010-09-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-21
        相关资源
        最近更新 更多