53

In C#, if I have a List<T>, and I have an object of type T, how can I replace a specific item in the List<T> with the object of type T?

Here is what I have tried:

List<CustomListItem> customListItems = new List<CustomListItem>();
CustomListItem customListItem1 = new CustomListItem() { name = "Item 1", date = DateTime.MinValue};
CustomListItem customListItem2 = new CustomListItem() { name = "Item 2", date = DateTime.MinValue };
CustomListItem customListItem3 = new CustomListItem() { name = "Item 3", date = DateTime.MinValue };

customListItems.Add(customListItem1);
customListItems.Add(customListItem2);
customListItems.Add(customListItem3);

CustomListItem newCustomListItem = new CustomListItem() { name = "Item 4", date = DateTime.Now };

customListItem2 = customListItems.Where(i=> i.name == "Item 2").First();
customListItem2 = newCustomListItem;

In the above code, I want to replace the customListItem2 with the newCustomListItem.

Do I have to remove the item in the list, and then insert the new item? Can I not do a simple assignment of customListItem2 = newCustomListItem?

What is the most efficient way of replacing an item in a list with another item?

starball
  • 20,030
  • 7
  • 43
  • 238
Simon
  • 7,991
  • 21
  • 83
  • 163
  • 2
    Have you checked http://stackoverflow.com/questions/17188966/how-to-replace-list-item-in-best-way? – haim770 Nov 05 '14 at 08:51
  • Additional to the solutions that have already been posted you should also implement an Equals/GetHashCode for your custom type, otherwise your Where/Index-operation may fail to find the right element. – MakePeaceGreatAgain Nov 05 '14 at 09:01

5 Answers5

92

You have to replace the item, not the value of customListItem2. Just replace following:

customListItem2 = customListItems.Where(i=> i.name == "Item 2").First();
customListItem2 = newCustomListItem;

With this:

customListItem2 = customListItems.Where(i=> i.name == "Item 2").First();
var index = customListItems.IndexOf(customListItem2);

if(index != -1)
    customListItems[index] = newCustomListItem;

Edit:

As Roman R. stated in a comment, you can replace the .Where(predicate).First() by a simple First(predicate):

customListItem2 = customListItems.First(i=> i.name == "Item 2");
Community
  • 1
  • 1
Abbas
  • 14,186
  • 6
  • 41
  • 72
  • 1
    You can replace the .Where with .First in the first place and delete it from the end. It will be the same result but with cleaner and faster code. Do it like the following: customListItem2 = customListItems.First(i=> i.name == "Item 2"); – Roman R. Jul 07 '16 at 10:04
  • 2
    Using this code, is it possible that the index will be -1? After all, if it wasn't on the list, First() would throw an error first :) – Bartosz Apr 10 '18 at 20:20
  • 1
    then use `FirstOrDefault()` which will return null if not found, then -1 for the `IndexOf(null)` – CAD bloke Jun 18 '19 at 01:47
  • 2
    @RomanR. - It will make it only cleaner; it's already proven that `First(expresion)` is slower than `Where(expresion).First()`. Useful link: https://stackoverflow.com/questions/8663897/why-is-linq-wherepredicate-first-faster-than-firstpredicate – HellBaby Aug 07 '19 at 13:24
  • @HellBaby seems to be outdated. Have a look at this https://dotnetfiddle.net/OrUUSG Tried it myself and verified the results. – Roman R. Aug 19 '19 at 09:56
  • EDIT: Depends on the parameters your passing. Change n to 90 and First without Where is slower... – Roman R. Aug 19 '19 at 10:58
  • 1
    @HellBaby interesting observation - visual studio will try to underline it and suggest that you make it .First instead of .Where().First though despite the slowness – ina Aug 13 '20 at 20:53
12
var customListItems = new List<CustomListItem>();
var customListItem1 = new CustomListItem() { name = "Item 1", date = DateTime.MinValue };
var customListItem2 = new CustomListItem() { name = "Item 2", date = DateTime.MinValue };
var customListItem3 = new CustomListItem() { name = "Item 3", date = DateTime.MinValue };

customListItems.Add(customListItem1);
customListItems.Add(customListItem2);
customListItems.Add(customListItem3);

var newCustomListItem = new CustomListItem() { name = "Item 4", date = DateTime.Now };

customListItems[customListItems.FindIndex(x => x.name == "Item 2")] = newCustomListItem;

or

public static class ListExtensions
{
    public static void Replace<T>(this List<T> list, Predicate<T> oldItemSelector , T newItem)
    {
        //check for different situations here and throw exception
        //if list contains multiple items that match the predicate
        //or check for nullability of list and etc ...
        var oldItemIndex = list.FindIndex(oldItemSelector);
        list[oldItemIndex] = newItem;
    }
}

and then

customListItems.Replace(x => x.name == "Item 2", newCustomListItem);
Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
  • Does the extension approach use reflection? It is certainly more convenient with this code, and I went this route. Just wondering about any performance hit. – dunwan Sep 14 '16 at 11:53
  • No, it does not use reflection. @dunwan – Hamid Pourjam Sep 14 '16 at 11:55
  • 2
    With this solution the list is only looped once. With the one in the accepted answer twice. But often or usually the not-found-case should be handled. If the item is not found FindIndex returns -1. – noox Dec 20 '17 at 10:05
2

if sequence of list not matter to you then you can try this

CustomListItem newCustomListItem = new CustomListItem() { name = "Item 4", date = DateTime.Now };

customListItem2 = customListItems.Where(i=> i.name == "Item 2").First();  

customListItems.Remove(customListItem2);
customListItems.Add(newCustomListItem );
2

You can use a select to build a new iEnumerable with replace item :

customListItems.Select(x => x.name == "Item 2" ? newItem : x).ToList();

i did not test for performance but I found this solution quite concise and explicit.

Olivier Duhart
  • 362
  • 3
  • 14
-1

An alternative would be to use the DynamicData package that has lot of others facilities on lists:

Quick Answer

make sure the package is installed:

Install-Package DynamicData -Version 7.4.3

then

using DynamicData;

DynamicData.ListEx.Replace<CustomListItem>(customListItems, customListItem2, newCustomListItem);

The following is the complete working solution.

Working Example

my custom item class

public class CustomListItem
{
    public string name; 
    public DateTime date;

    public override string ToString()
    {
        return name + ", " + date.ToString();
    }
}

In the main try this, make sure to comment out the assignment before the replacement.

static void Main(string[] args)
{
    List<CustomListItem> customListItems = new List<CustomListItem>();
    CustomListItem customListItem1 = new CustomListItem() { name = "Item 1", date = DateTime.MinValue };
    CustomListItem customListItem2 = new CustomListItem() { name = "Item 2", date = DateTime.MinValue };
    CustomListItem customListItem3 = new CustomListItem() { name = "Item 3", date = DateTime.MinValue };

    customListItems.Add(customListItem1);
    customListItems.Add(customListItem2);
    customListItems.Add(customListItem3);

    CustomListItem newCustomListItem = new CustomListItem() { name = "Item 4", date = DateTime.Now };

    customListItem2 = customListItems.Where(i => i.name == "Item 2").First();
    //customListItem2 = newCustomListItem;


    DynamicData.ListEx.Replace<CustomListItem>(customListItems, customListItem2, newCustomListItem);

    foreach(var customListItem in customListItems)
    {
        Debug.Print("\n" + customListItem.ToString());
    }
}

references: https://github.com/reactivemarbles/DynamicData.
https://dynamic-data.org
E_net4
  • 27,810
  • 13
  • 101
  • 139
Oliamster
  • 486
  • 5
  • 10