198

So I have a generic list, and an oldIndex and a newIndex value.

I want to move the item at oldIndex, to newIndex...as simply as possible.

Any suggestions?

Note

The item should be end up between the items at (newIndex - 1) and newIndex before it was removed.

Richard Ev
  • 52,939
  • 59
  • 191
  • 278
  • 2
    You should change the answer you ticked. The one with `newIndex--` does not result in the behaviour you said you wanted. – Miral Jan 31 '14 at 09:05
  • 2
    @Miral - which answer do you think should be the accepted one? – Richard Ev Jan 31 '14 at 11:09
  • 4
    jpierson's. It results in the object that used to be at oldIndex before the move to be at newIndex after the move. This is the least surprising behaviour (and it's what I needed when I was writing some drag'n'drop reordering code). Granted he's talking about `ObservableCollection` and not a generic `List`, but it's trivial to simply swap the method calls to get the same result. – Miral Jan 31 '14 at 21:35
  • The requested (and [correctly implemented in this answer](http://stackoverflow.com/a/450250/2444725)) behavior to move the item in between the items at `[newIndex - 1]` and `[newIndex]` is not invertible. `Move(1, 3); Move(3, 1);` doesn't return the list to the initial state. Meanwhile there is different behavior provided in `ObservableCollection` and [mentioned in this answer](http://stackoverflow.com/a/5481789/2444725), which is [invertible](http://stackoverflow.com/a/10471323/2444725). – Lightman Feb 01 '17 at 05:45

10 Answers10

178

I know you said "generic list" but you didn't specify that you needed to use the List(T) class so here is a shot at something different.

The ObservableCollection(T) class has a Move method that does exactly what you want.

public void Move(int oldIndex, int newIndex)

Underneath it is basically implemented like this.

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

So as you can see the swap method that others have suggested is essentially what the ObservableCollection does in it's own Move method.

UPDATE 2015-12-30: You can see the source code for the Move and MoveItem methods in corefx now for yourself without using Reflector/ILSpy since .NET is open source.

jpierson
  • 16,435
  • 14
  • 105
  • 149
  • 38
    I wonder why this isn't implemented on the List as well, any one to shed some light on this? – Andreas Jan 17 '14 at 09:47
  • 2
    What is the difference between a generic list and a List(T) class? I thought they were the same :( – BenKoshy Aug 24 '15 at 02:38
  • 2
    A "generic list" could mean any type of list or collection like data structure in .NET which could include ObservableCollection(T) or other classes which may implement a _listy_ interface such as IList/ICollection/IEnumerable. – jpierson Oct 30 '15 at 01:00
  • 8
    Could someone explain, please, why there is no destination index shift (in case it is larger than source index) indeed? – Vladius Jan 22 '16 at 12:31
  • @vladius I believe the idea is that the newIndex value specified should simply specify the desired index that the item should be after the move and since an insert is being used there is no reason for adjustment. If newIndex was a position relative to the original index that would be another story I guess but that's not how it works. – jpierson Feb 01 '17 at 04:52
  • Move in a list would require all the values shift, it is slow, so it's not built in. i think – Jason Jul 24 '17 at 19:14
  • 1
    @Jason isn't it just a linked list underneath? When you remove and insert you aren't shifting data around. I'm in the camp that Microsoft just never completes anything... They get it close enough and move to a new product. Look at all their enterprise solutions.. It's a mess. – visc Apr 18 '18 at 13:07
  • @visc List is implemented as ArrayList and ObservableCollection uses a List underneath – Firo Nov 07 '18 at 15:46
157
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);

Put into Extension methods they look like:

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

    }
JKennedy
  • 18,150
  • 17
  • 114
  • 198
Garry Shutler
  • 32,260
  • 12
  • 84
  • 119
  • 10
    Your solution breaks down if there are two copies of item in the list, with one occurring before oldIndex. You should use RemoveAt to make sure you get the right one. – Aaron Maenpaa Jan 16 '09 at 12:27
  • @Garry, I'm sorry for deleting your comment on my other answer but since we don't care about where the item on the newIndex goes before the move, in this case, moving is as good as swapping the items. – bruno conde Jan 16 '09 at 13:04
  • 1
    @GarryShutler I can't see how the index could shift if we are removing and then inserting a single item. Decrementing the `newIndex` actually breaks my test (see my answer below). – Ben Foster Aug 07 '12 at 10:45
  • @BenFoster If the new index is after the old index then the new index would be one less after the `RemoveAt`. For example: you want to move `list[15]` to be between the items at `list[25]` and `list[26]`. You remove `list[15]` so now what was `list[25]` is now `list[24]` and what was `list[26]` is now `list[25]`, so instead of `list.Insert(26, item);` you would use `list.Insert(25, item);`. It is possible that the code above is not working for you because of how you calculate which index you wish to use. – Trisped Oct 03 '12 at 21:30
  • @Trisped, you're right, this comes down to how you calculate the new index. My behaviour does not take into account the original list positions - if you `Move(0, 2)` it will take `list[0]`, remove it, then insert it at `list[2]`. It looks as if this is not the behaviour that the OP requires. – Ben Foster Oct 29 '12 at 14:08
  • 1
    Note: if thread-safety is important, this should all be inside a `lock` statement. – rory.ap Apr 10 '19 at 14:17
  • 2
    I wouldn't use this as it is confusing for several reasons. Defining a method Move(oldIndex,newIndex) on a list and calling Move(15,25) and then Move(25,15) is not an identity but swap. Also Move(15,25) makes the item move to index 24 and not at 25 which I would expect. Besides swapping can be implemented by temp=item[oldindex]; item[oldindex]=item[newindex]; item[newindex]=temp; which seems more efficient on large arrays. Also Move(0,0) and Move(0,1) would be the same which is also odd. And also Move(0, Count -1) doesn't move the item to the end. – Wouter May 08 '19 at 21:17
  • For Gong-wpf-dragdrop a edge case will break the code when droping on las position. Condition shall be updated with if (newIndex < list.Count - 1 && newIndex > oldIndex) newIndex--; Also before move check (oldIndex != newIndex) – Vlad Isoc May 26 '20 at 09:42
16

I know this question is old but I adapted THIS response of javascript code to C#. Hope it helps

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;
}
mxmissile
  • 11,464
  • 3
  • 53
  • 79
Francisco
  • 301
  • 3
  • 8
14

List<T>.Remove() and List<T>.RemoveAt() do not return the item that is being removed.

Therefore you have to use this:

var item = list[oldIndex];
list.RemoveAt(oldIndex);
list.Insert(newIndex, item);
M4N
  • 94,805
  • 45
  • 217
  • 260
8

I created an extension method for moving items in a list.

An index should not shift if we are moving an existing item since we are moving an item to an existing index position in the list.

The edge case that @Oliver refers to below (moving an item to the end of the list) would actually cause the tests to fail, but this is by design. To insert a new item at the end of the list we would just call List<T>.Add. list.Move(predicate, list.Count) should fail since this index position does not exist before the move.

In any case, I've created two additional extension methods, MoveToEnd and MoveToBeginning, the source of which can be found 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);
        };
    }
}
Ben Foster
  • 34,340
  • 40
  • 176
  • 285
  • Where is `Ensure.Argument`defined? – Oliver Aug 07 '12 at 11:21
  • 1
    In a normal `List` you can call `Insert(list.Count, element)` to place something at the end of the list. So your `When_new_index_is_higher` should call `list.Move(x => x == 3, 5)` which actually fails. – Oliver Aug 07 '12 at 11:38
  • 3
    @Oliver in a normal `List` I would just call `.Add` to insert a *new* item to the end of a list. When *moving* individual items we never increase the original size of the index since we're only removing a single item and reinserting it. If you click the link in my answer you'll find the code for `Ensure.Argument`. – Ben Foster Aug 08 '12 at 09:02
  • You solution expects that the destination index is a position, not between two elements. While this works well for some use cases, it does not work for others. Also, your move does not support moving to the end (as noted by Oliver) but no where in your code do you indicate this constraint. It is also counterintuitive, if I have a list with 20 elements and want to move element 10 to the end, I would expect the Move method to handle this, rather then having to find to save the object reference, remove the object from the list and add the object. – Trisped Oct 03 '12 at 21:44
  • 1
    @Trisped actually if you read my answer, moving an item to the end/beginning of the list *is* supported. You can see the specs [here](https://github.com/benfoster/Fabrik.Common/blob/master/src/Specs/Fabrik.Common.Specs/ListExtensionSpecs.cs). Yes my code expects the index to be a valid (existing) position within the list. We are *moving* items, not inserting them. – Ben Foster Oct 29 '12 at 12:57
6

Insert the item currently at oldIndex to be at newIndex and then remove the original instance.

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

You have to take into account that the index of the item you want to remove may change due to the insertion.

Palec
  • 12,743
  • 8
  • 69
  • 138
Megacan
  • 2,510
  • 3
  • 20
  • 31
2

I would expect either:

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

... or:

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

... would do the trick, but I don't have VS on this machine to check.

Aaron Maenpaa
  • 119,832
  • 11
  • 95
  • 108
  • 1
    @GarryShutler It depends on the situation. If your interface allows the user to specify the position in the list by index, they will be confused when they tell item 15 to move to 20, but instead it moves to 19. If your interface allows the user to drag an item between to others on the list then it would make sense to decrement `newIndex` if it is after `oldIndex`. – Trisped Oct 03 '12 at 21:47
-1

Simplest way:

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

EDIT

The question isn't very clear ... Since we don't care where the list[newIndex] item goes I think the simplest way of doing this is as follows (with or without an extension method):

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

This solution is the fastest because it doesn't involve list insertions/removals.

bruno conde
  • 47,767
  • 15
  • 98
  • 117
-4

This is how I implemented a move element extension method. It handles moving before/after and to the extremes for elements pretty well.

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

Is more simple guys just do this

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

Do the same with MoveDown but change the if for "if (ind !=Concepts.Count())" and the Concepts.Insert(ind+1,item.ToString());

Richard Aguirre
  • 543
  • 6
  • 14