14

I have a list that gets filled in with some data from an operation and I am storing it in the memory cache. Now I want another list which contains some sub data from the list based on some condition.

As can be seen in the below code I am doing some operation on the target list. The problem is that whatever changes I am doing to the target list is also being done to the mainList. I think its because of the reference is same or something.

All I need is that operation on the target list not affect data inside the main list.

List<Item> target = mainList;
SomeOperationFunction(target);

 void List<Item> SomeOperationFunction(List<Item> target)
{
  target.removeat(3);
  return target;
}
Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
user505210
  • 1,362
  • 11
  • 28
  • 50
  • 1
    Are you saying that objects present in both lists get modified? I.e. do you need to Clone() /create a copy of them instead of working on the same instances? – Oli Dec 20 '12 at 18:01

11 Answers11

25

You need to clone your list in your method, because List<T> is a class, so it's reference-type and is passed by reference.

For example:

List<Item> SomeOperationFunction(List<Item> target)
{
  List<Item> tmp = target.ToList();
  tmp.RemoveAt(3);
  return tmp;
}

Or

List<Item> SomeOperationFunction(List<Item> target)
{
  List<Item> tmp = new List<Item>(target);
  tmp.RemoveAt(3);
  return tmp;
}

or

List<Item> SomeOperationFunction(List<Item> target)
{
  List<Item> tmp = new List<Item>();
  tmp.AddRange(target);
  tmp.RemoveAt(3);
  return tmp;
}
Leri
  • 12,367
  • 7
  • 43
  • 60
  • The select doesn't do anything here, you only need `ToList`. – Servy Dec 20 '12 at 18:03
  • 1
    The third example is just completely wrong. The `tmp` var is being used to populate itself. Then you are still removing the element from `target` instead of `tmp'`. – Jesse Webb Dec 20 '12 at 18:06
  • @PLB `ToList` creates a new list. It takes an `IEnumerable` as an input. It's contents is basically nothing more than `return new List(source);` – Servy Dec 20 '12 at 18:08
  • @PLB I believe all the LINQ expressions, `ToList()` included, return new copies of the data so that the operations can assume the original data is immutable. – Jesse Webb Dec 20 '12 at 18:08
  • @JesseWebb It has to return a new list because the source sequence is unlikely to be a list, so it needs to create a new list for almost all of the other cases. It would be confusing, error prone, and a source of much confusion if it detected that the source sequence was already a list and didn't create a new one. – Servy Dec 20 '12 at 18:14
  • @JesseWebb The third example should and works as expected. http://ideone.com/ffHPJV – Leri Dec 20 '12 at 18:18
  • Why the `RemoveAt(3)` ? – Nicke Manarin Mar 20 '15 at 20:21
  • @NickeManarin Copied from OP. Proper way is to pass int argument – Leri Mar 21 '15 at 18:15
8

You need to make a copy of the list so that changes to the copy won't affect the original. The easiest way to do that is to use the ToList extension method in System.Linq.

var newList = SomeOperationFunction(target.ToList());
Servy
  • 202,030
  • 26
  • 332
  • 449
  • 4
    @Bernhof: This is correct; however, keep in mind that the list still holds references to the same items if the items are of a reference type. So changes to the items themselves will still affect the items in both lists. – Olivier Jacot-Descombes Dec 20 '12 at 18:06
  • A minor point to make, but I would argue that `SomeOperationFunction` should handle the copying. One would expect as much if the function *returns* a `List`. – bernhof Dec 20 '12 at 18:31
  • @Bernhof Probably, yeah. Given that like 5 other people suggested that change I didn't see the need to restate it. The point is merely that the copying needs to take place somewhere. – Servy Dec 20 '12 at 18:35
5

Build a new list first and operate on that, because List is a reference type, i.e. when you pass it in a function, you do not just pass the value but the actual object itself.

If you just assign target to mainList, both variables point to the same object, so you need to create a new List:

List<Item> target = new List<Item>(mainList);

void List<Item> SomeOperationFunction() makes no sense, because either you return nothing (void) or you return a List<T>. So either remove the return statement from your method or return a new List<Item>. In the latter case, I would rewrite this as:

List<Item> target = SomeOperationFunction(mainList);

List<Item> SomeOperationFunction(List<Item> target)
{
    var newList = new List<Item>(target);
    newList.RemoveAt(3);
    return newList;
}
Residuum
  • 11,878
  • 7
  • 40
  • 70
  • `List is a reference type and therefore passed by reference` -- is circular. If you say this, you're going to have to explain what a reference type is, or link to a reference (no pun intended). – Robert Harvey Dec 20 '12 at 18:06
  • And of course you still are passing the reference by value, not by reference, unless you have the `ref` keyword. It's important to distinguish passing a reference by value versus a value by reference. – Servy Dec 20 '12 at 18:10
3

I tried many of the answers above. On all the ones I tested, an update to the new list modifies the original. this is what works for me.

var newList = JsonConvert.DeserializeObject<List<object>>(JsonConvert.SerializeObject(originalList));
return newlist.RemoveAt(3);
Vernard Sloggett
  • 336
  • 4
  • 10
2

Even if you create a new list, the references to the items in the new list will still point to the items in the old list, so I like to use this extension method if I need a new list with new references...

public static IEnumerable<T> Clone<T>(this IEnumerable<T> target) where T : ICloneable
{
    If (target.IsNull())
        throw new ArgumentException();

    List<T> retVal = new List<T>();

    foreach (T currentItem in target)
        retVal.Add((T)(currentItem.Clone()));

    return retVal.AsEnumerable();
}
Quanta
  • 465
  • 5
  • 14
1

Your target variable is a reference type. This means that anything you do to it will be reflected in the list you pass into it.

To not do that, you are going to need to create a new list in the method, copy target contents to it, and then perform the remove at operation on the new list.

About Reference and Value Types

kemiller2002
  • 113,795
  • 27
  • 197
  • 251
1

Since a List is a reference type, what is passed to the function is a reference to the original list.

See this MSDN article for more information about how parameters are passed in C#.

In order to achieve what you want, you should create a copy of the list in SomeOperationFunction and return this instead. A simple example:

void List<Item> SomeOperationFunction(List<Item> target)
{
  var newList = new List<Item>(target);
  newList.RemoveAt(3);
  return newList; // return copy of list
}

As pointed out by Olivier Jacot-Descombes in the comments to another answer, it is important to bear in mind that

[...] the list still holds references to the same items if the items are of a reference type. So changes to the items themselves will still affect the items in both lists.

bernhof
  • 6,219
  • 2
  • 45
  • 71
  • 1
    perhaps you should spend some time reading your own link. Reference types aren't passed by reference, they're passed by value, just like value types, but what's passed *is a reference*. It's a very important difference. – Servy Dec 20 '12 at 18:15
  • 'scuse the typo, I understand the difference and have edited the answer to avoid misunderstandings. – bernhof Dec 20 '12 at 18:25
0

Your are seeing the original list being modified because, by default, any non-primitive objects, are passed by reference (It is actually pass by value, the value being the reference, but that is a different matter).

What you need to do is clone the object. This question will help you with some code to clone a List in C#: How do I clone a generic list in C#?

Community
  • 1
  • 1
Jesse Webb
  • 43,135
  • 27
  • 106
  • 143
  • 1
    It doesn't matter at all if the list is passed by reference or by value. In any case `target` will be a reference pointing to the original list. By value/by reference makes only a difference if you assign a new value to `target` as in `target = null;`. – Olivier Jacot-Descombes Dec 20 '12 at 18:09
  • @OlivierJacot-Descombes - That is why I simplified by saying it is pass by reference. The comment in the parenthesis is just stating that all variables in C# are pass by value unless you explicitely use the `ref` keyword. It just so happens that the 'value' used for complex types is a pointer. – Jesse Webb Dec 20 '12 at 18:10
  • `string` is a primitive type but is a reference type (it's immutable so). `System.Drawing.Point` is not a primitive type but is a value type (a struct). – Olivier Jacot-Descombes Dec 20 '12 at 18:14
  • @OlivierJacot-Descombes - `string` is not a primitive type, it is a reference type (it is just an alias to `String`). And `Point` is a `struct`, which all are value types. The reason I made the comment about 'pass by reference' is because the OP didn't even use the word 'reference' so I wasn't even sure he was aware of what value/reference types are. Even if 'in any case', the var will be a reference to the original list, I thought it would be good to help the OP understand _why_ as opposed to just tell him how to fix it. – Jesse Webb Dec 21 '12 at 14:38
  • There seems to be some confusion about what is a primitive type [this MSDN article](http://msdn.microsoft.com/en-us/magazine/cc301569.aspx) says "Any data types directly supported by the compiler are called primitive types". The expression `typeof(string).IsPrimitive` returns `false`. – Olivier Jacot-Descombes Dec 21 '12 at 15:41
0

Instead of assigning mainList to target, I would do: target.AddRange(mainList);

Then you will have a copy of the items instead of a reference to the list.

Sam Axe
  • 33,313
  • 9
  • 55
  • 89
  • Why call `ToArray`? `AddRange` takes an `IEnumerable`, which the list already implements. – Servy Dec 20 '12 at 18:06
  • I've run in to instances (perhaps .net 2.0 or 3.5) where `AddRange` only took an array. I'm happy to modify my answer if the latest framework accepts an `IEnumerable` – Sam Axe Dec 20 '12 at 18:09
  • The `IEnumerable` overload was added with .NET 2.0. [link](http://msdn.microsoft.com/en-us/library/z883w3dc(v=vs.80).aspx). The only version that didn't have it was 1.0. – Servy Dec 20 '12 at 18:11
  • Weird. Perhaps I'm thinking of something else that had an AddRange function. Thanks for the correction and reference link. – Sam Axe Dec 20 '12 at 18:12
0

Just make sure that you initialize the new list with a list created by copying the elements of the source list.

List<Item> target = mainList; Should be List<item> target = new List<Item>(mainList);

Kavet Kerek
  • 1,305
  • 9
  • 24
0

You'll need to make a copy of the list since in your original code what you're doing is just passing around, as you correctly suspected, a reference (someone would call it a pointer).

You could either call the constructor on the new list, passing the original list as parameter:

List<Item> SomeOperationFunction(List<Item> target)
{
    List<Item> result = new List<Item>(target);
    result.removeat(3);
    return result;
}

Or create a MemberWiseClone:

List<Item> SomeOperationFunction(List<Item> target)
{
    List<Item> result = target.MemberWiseClone();
    result.removeat(3);
    return result;
}

Also, you are not storing the return of SomeOperationFunction anywhere, so you might want to revise that part as well (you declared the method as void, which should not return anything, but inside it you're returning an object). You should call the method this way:

List<Item> target = SomeOperationFunction(mainList);

Note: the elements of the list will not be copied (only their reference is copied), so modifying the internal state of the elements will affect both lists.

Nadir Sampaoli
  • 5,437
  • 4
  • 22
  • 32