ToList (Linq) doesn't appear to copy references - c#

I have code that looks something like this:
var selectedItems = ItemList.SelectedItems().ToList();
var selectedItems2 = ItemList2.SelectedItems().ToList();
selectedItems[0] = selectedItems2[0];
Here's a look at the SelectedItems extension method:
public static IEnumerable<T> SelectedItems<T>(this IEnumerable<T> source) where T : ISelectable
{
return source.Where(s => s.IsSelected);
}
Whenever I do a ReferenceEquals(ItemList, selectedItems), the value returns false and whenever I modify a value within any of the lists, the changes aren't reflected in the other list.
The Items in the ItemList are reference types (custom made classes).
What am I doing wrong?
EDIT:
Here's the original code. I didn't want to overcomplicate things but here it is nonetheless:
var test = Map.TileMap.Layers[0].TileList.SelectedItems().ToList();
if (ReferenceEquals(test[0], Map.TileMap.Layers[0].TileList[0]))
{
// returns true
}
var tileset2D = Tileset.TileMap.Layers[0].TileList.SelectedItems().To2D(t => t.SelectableRegion.Y).ToList();
test[0] = tileset2D[0][0];
// test[0] has changed but Tileset.TileMap.Layers[0].TileList[0] hasn't changed.
Here's the To2D extension method:
public static IEnumerable<IEnumerable<T>> To2D<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source), "Source collection cannot be null");
}
return source.GroupBy(keySelector);
}

Calling .ToList() isn't a casting operation. It generates a brand new list object and populates it with the results of the enumerable prior to .ToList(). The references aren't the same because they are different lists.
The SelectedItems method should be returning instances of the original list. So you should be able to test that it works.
With this sample code:
var ItemList = new List<Selectable>()
{
new Selectable() { IsSelected = true },
new Selectable() { IsSelected = false },
};
var selectedItems = ItemList.SelectedItems().ToList();
Console.WriteLine(selectedItems[0].IsSelected);
ItemList[0].IsSelected = false;
Console.WriteLine(selectedItems[0].IsSelected);
I get the results:
True
False
My implementation of Selectable is:
public class Selectable : ISelectable
{
public bool IsSelected { get; set; }
}
You must have something happening in code you haven't shown.
You've added some more code, and now the snippit .GroupBy(t => t.SelectableRegion.Y) makes me think that the grouping isn't working as expected. You should test your assumptions about what the grouping key returns
My suggestion, at this point, is that you avoid recreating extension methods. It would be better to write source.Where(s => s.IsSelected).GroupBy(t => t.SelectableRegion.Y) than source.SelectedItems().To2D(t => t.SelectableRegion.Y). This kind of thing just confuses the code and makes it harder to reason about. The kinds of errors you're experience surface far too often.

The problem was that I was assigning a new reference to my list item
test[0] = tileset2D[0][0];
So now test[0] is point to the same address in memory as tileset2D[0][0]. That's why the values in test[0] weren't modifying values inside Map.TileMap.Layers[0].TileList.
So I basically have to create some time of DeepClone method that will copy the values and clone the references without assigning a new reference to my TileList item.
Thanks for all the help :)

Related

Extension method on ICollection<T> doesn't change the calling list?

I want to create an extension method that runs on a List and accept another list:
public static void Charge<T, S>(this ICollection<T> targetList, ICollection<S> sourceList) where T : class, new()
{
if (targetList == null || sourceList == null)
throw new NullReferenceException();
targetList = new List<T>();
foreach (var item in sourceList)
{
T t = new T();
//do work on t
targetList.Add(t);
}
}
however when I call it like this:
var targetList = new List<Item>();
targetList.Charge(sourceList);
the targetList doesn't change (items count = 0)
The proposed method makes no sense to me.
You want to copy the contents of the source list into the target, but you first want to replace the target list to make sure it's empty? If you're going to replace the target list anyway, why not simply replace it like so?
target = source.ToList();
Also, how do you propose to implement the "do some work on t" in a generic extension method where the types of S and T are not known? Why not do the idiomatic thing, e.g.:
target = source.Select(s => Transform(s)).ToList();
Here, we assume Transform is a method capable of creating and populating a target object from a source object.
Or, you could avoid reallocating a new list by clearing the old one first:
target.Clear();
target.AddRange(source.Select(s => Transform(s)));
And if you really want to have a single invocation, you could simply wrap either of the alternatives above, e.g.:
public static List<TTarget> ToList<TSource, TTarget>(
this IEnumerable<TSource> source,
Func<TSource, TTarget> conversion)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (conversion == null)
throw new ArgumentNullException(nameof(conversion));
return source.Select(conversion).ToList();
}
Usage:
target = source.ToList(s => Transform(s));
You can't assign a new instance to targetList if you pass the list by value. You can add or remove or modify the content of the existing list, but if you want to assign another instance you need to add the ref keyword to allow assignments.

OrderBy without lambda for simple IEnumerables

Is there a simpler way to write the following? I.E., without the lambda.
var strings = new[] { "Alabama", "Mississippi", "Louisiana" };
var ordered = strings.OrderBy(x => x);
Seems like it should be possible, since string implements IEquatable<string>.
It's IComparable that matters more thanIEquatable here, but it is possible:
Array.Sort(strings);
This works because strings is already an array. Since you asked for any IEnumerable:
var ary = strings.ToArray();
Array.Sort(ary);
Note the extra variable is also important in this second sample, because Array.Sort() sorts the actual object passed without returning the results, and calling .ToArray() created a new array that was then thrown away. Without the extra variable, you lose your work.
There is a similar sort method on the List<T> object you can use, as well.
You can also make your own extension method for this:
public static class MyExtensions
{
public static IOrderedEnumerable<T> Sort(this IEnumerable<T> items) where T : IComparable
{
return items.OrderBy(i => i);
}
}
And now you could just say:
var ordered = strings.Sort();
For .NET 7 or higher, use Order.
var strings = new[] { "Alabama", "Mississippi", "Louisiana" };
var ordered = strings.Order();
dotnet/runtime#67194

Remove specific entry from list (beginner in c#)

I have a simple static inventory class which is a list of custom class Item. I am working on a crafting system and when I craft something I need to remove the required Items from my inventory list.
I tried to create a method that I can call which takes an array of the items to remove as a parameter, but its not working.
I think its because the foreach loop doesn't know which items to remove? I am not getting an error messages, it just doesn't work. How can I accomplish this?
public class PlayerInventory: MonoBehaviour
{
public Texture2D tempIcon;
private static List<Item> _inventory=new List<Item>();
public static List<Item> Inventory
{
get { return _inventory; }
}
public static void RemoveCraftedMaterialsFromInventory(Item[] items)
{
foreach(Item item in items)
{
PlayerInventory._inventory.Remove(item);
}
}
}
Here is the function that shows what items will be removed:
public static Item[] BowAndArrowReqs()
{
Item requiredItem1 = ObjectGenerator.CreateItem(CraftingMatType.BasicWood);
Item requiredItem2 = ObjectGenerator.CreateItem(CraftingMatType.BasicWood);
Item requiredItem3 = ObjectGenerator.CreateItem(CraftingMatType.String);
Item[] arrowRequiredItems = new Item[]{requiredItem1, requiredItem2, requiredItem3};
return arrowRequiredItems;
}
And here is where that is called:
THis is within the RecipeCheck static class:
PlayerInventory.RemoveCraftedMaterialsFromInventory(RecipeCheck.BowAndArrowReqs());
While I like Jame's answer (and it sufficiently covers the contracts), I will talk on how one might implement this equality and make several observations.
For starts, in the list returned there may be multiple objects of the same type - e.g. BasicWood, String. Then there needs to be a discriminator used for each new object.
It would be bad if RemoveCraftedMaterialsFromInventory(new [] { aWoodBlock }) to remove a Wood piece in the same way that two wood pieces were checked ("equals") to each other. This is because being "compatible for crafting" isn't necessarily the same as "being equals".
One simple approach is to assign a unique ID (see Guid.NewGuid) for each specific object. This field would be used (and it could be used exclusively) in the Equals method - however, now we're back at the initial problem, where each new object is different from any other!
So, what's the solution? Make sure to use equivalent (or identical objects) when removing them!
List<Item> items = new List<Item> {
new Wood { Condition = Wood.Rotten },
new Wood { Condition = Wood.Epic },
};
// We find the EXISTING objects that we already have ..
var woodToBurn = items.OfType<Wood>
.Where(w => w.Condition == Wood.Rotten);
// .. so we can remove them
foreach (var wood in woodToBurn) {
items.Remove(wood);
}
Well, okay, that's out of the way, but then we say: "How can we do this with a Recipe such that Equals isn't butchered and yet it will remove any items of the given type?"
Well, we can either do this by using LINQ or a List method that supports predicates (i.e. List.FindIndex) or we can implement a special Equatable to only be used in this case.
An implementation that uses a predicate might look like:
foreach (var recipeItem in recipeItems) {
// List sort of sucks; this implementation also has bad bounds
var index = items.FindIndex((item) => {
return recipeItem.MaterialType == item.MaterialType;
});
if (index >= 0) {
items.RemoveAt(index);
} else {
// Missing material :(
}
}
If class Item doesn't implement IEquatable<Item> and the bool Equals(Item other) method, then by default it will use Object.Equals which checks if they are the same object. (not two objects with the same value --- the same object).
Since you don't say how Item is implemented, I can't suggest how to write it's Equals(), however, you should also override GetHashCode() so that two Items that are Equal return the same hash code.
UPDATE (based on comments):
Essentially, List.Remove works like this:
foreach(var t in theList)
{
if (t.Equals(itemToBeRemove))
PerformSomeMagicToRemove(t);
}
So, you don't have to do anything to the code you've given in your question. Just add the Equals() method to Item.

Remove items from list 1 not in list 2

I am learning to write lambda expressions, and I need help on how to remove all elements from a list which are not in another list.
var list = new List<int> {1, 2, 2, 4, 5};
var list2 = new List<int> { 4, 5 };
// Remove all list items not in List2
// new List Should contain {4,5}
// The lambda expression is the Predicate.
list.RemoveAll(item => item. /*solution expression here*/ );
// Display results.
foreach (int i in list)
{
Console.WriteLine(i);
}
You can do this via RemoveAll using Contains:
list.RemoveAll( item => !list2.Contains(item));
Alternatively, if you just want the intersection, using Enumerable.Intersect would be more efficient:
list = list.Intersect(list2).ToList();
The difference is, in the latter case, you will not get duplicate entries. For example, if list2 contained 2, in the first case, you'd get {2,2,4,5}, in the second, you'd get {2,4,5}.
Solution for objects (maybe easier than horaces solution):
If your list contains objects, rather than scalars, it is that simple, by removing by one selected property of the objects:
var a = allActivePatientContracts.RemoveAll(x => !allPatients.Select(y => y.Id).Contains(x.PatientId));
list = list.Except(list2).ToList();
This question has been marked as answered, but there is a catch. If your list contains an object, rather than a scalar, you need to do a bit more work.
I tried this over and over with Remove() and RemoveAt() and all sorts of things and none of them worked correctly. I couldn't even get a Contains() to work correctly. Never matched anything. I was stumped until I got the suspicion that maybe it could not match up the item correctly.
When I realized this, I refactored the item class to implement IEquatable, and then it started working.
Here is my solution:
class GenericLookupE : IEquatable<GenericLookupE>
{
public string ID { get; set; }
public bool Equals( GenericLookupE other )
{
if ( this.ID == other.ID ) return true;
return false;
}
}
After I did this, the above RemoveAll() answer by Reed Copsey worked perfectly for me.
See: http://msdn.microsoft.com/en-us/library/bhkz42b3.aspx

ToList()-- does it create a new list?

Let's say I have a class
public class MyObject
{
public int SimpleInt{get;set;}
}
And I have a List<MyObject>, and I ToList() it and then change one of the SimpleInt, will my change be propagated back to the original list. In other words, what would be the output of the following method?
public void RunChangeList()
{
var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}};
var whatInt = ChangeToList(objs );
}
public int ChangeToList(List<MyObject> objects)
{
var objectList = objects.ToList();
objectList[0].SimpleInt=5;
return objects[0].SimpleInt;
}
Why?
P/S: I'm sorry if it seems obvious to find out. But I don't have compiler with me now...
Yes, ToList will create a new list, but because in this case MyObject is a reference type then the new list will contain references to the same objects as the original list.
Updating the SimpleInt property of an object referenced in the new list will also affect the equivalent object in the original list.
(If MyObject was declared as a struct rather than a class then the new list would contain copies of the elements in the original list, and updating a property of an element in the new list would not affect the equivalent element in the original list.)
From the Reflector'd source:
public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
return new List<TSource>(source);
}
So yes, your original list won't be updated (i.e. additions or removals) however the referenced objects will.
ToList will always create a new list, which will not reflect any subsequent changes to the collection.
However, it will reflect changes to the objects themselves (Unless they're mutable structs).
In other words, if you replace an object in the original list with a different object, the ToList will still contain the first object.
However, if you modify one of the objects in the original list, the ToList will still contain the same (modified) object.
Yes, it creates a new list. This is by design.
The list will contain the same results as the original enumerable sequence, but materialized into a persistent (in-memory) collection. This allows you to consume the results multiple times without incurring the cost of recomputing the sequence.
The beauty of LINQ sequences is that they are composable. Often, the IEnumerable<T> you get is the result of combining multiple filtering, ordering, and/or projection operations. Extension methods like ToList() and ToArray() allow you to convert the computed sequence into a standard collection.
The accepted answer correctly addresses the OP's question based on his example. However, it only applies when ToList is applied to a concrete collection; it does not hold when the elements of the source sequence have yet to be instantiated (due to deferred execution). In case of the latter, you might get a new set of items each time you call ToList (or enumerate the sequence).
Here is an adaptation of the OP's code to demonstrate this behaviour:
public static void RunChangeList()
{
var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 });
var whatInt = ChangeToList(objs); // whatInt gets 0
}
public static int ChangeToList(IEnumerable<MyObject> objects)
{
var objectList = objects.ToList();
objectList.First().SimpleInt = 5;
return objects.First().SimpleInt;
}
Whilst the above code may appear contrived, this behaviour can appear as a subtle bug in other scenarios. See my other example for a situation where it causes tasks to get spawned repeatedly.
A new list is created but the items in it are references to the orginal items (just like in the original list). Changes to the list itself are independent, but to the items will find the change in both lists.
Just stumble upon this old post and thought of adding my two cents. Generally, if I am in doubt, I quickly use the GetHashCode() method on any object to check the identities. So for above -
public class MyObject
{
public int SimpleInt { get; set; }
}
class Program
{
public static void RunChangeList()
{
var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } };
Console.WriteLine("objs: {0}", objs.GetHashCode());
Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode());
var whatInt = ChangeToList(objs);
Console.WriteLine("whatInt: {0}", whatInt.GetHashCode());
}
public static int ChangeToList(List<MyObject> objects)
{
Console.WriteLine("objects: {0}", objects.GetHashCode());
Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode());
var objectList = objects.ToList();
Console.WriteLine("objectList: {0}", objectList.GetHashCode());
Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode());
objectList[0].SimpleInt = 5;
return objects[0].SimpleInt;
}
private static void Main(string[] args)
{
RunChangeList();
Console.ReadLine();
}
And answer on my machine -
objs: 45653674
objs[0]: 41149443
objects: 45653674
objects[0]: 41149443
objectList: 39785641
objectList[0]: 41149443
whatInt: 5
So essentially the object that list carries remain the same in above code. Hope the approach helps.
I think that this is equivalent to asking if ToList does a deep or shallow copy. As ToList has no way to clone MyObject, it must do a shallow copy, so the created list contains the same references as the original one, so the code returns 5.
ToList will create a brand new list.
If the items in the list are value types, they will be directly updated, if they are reference types, any changes will be reflected back in the referenced objects.
In the case where the source object is a true IEnumerable (i.e. not just a collection packaged an as enumerable), ToList() may NOT return the same object references as in the original IEnumerable. It will return a new List of objects, but those objects may not be the same or even Equal to the objects yielded by the IEnumerable when it is enumerated again
var objectList = objects.ToList();
objectList[0].SimpleInt=5;
This will update the original object as well. The new list will contain references to the objects contained within it, just like the original list. You can change the elements either and the update will be reflected in the other.
Now if you update a list (adding or deleting an item) that will not be reflected in the other list.
I don't see anywhere in the documentation that ToList() is always guaranteed to return a new list. If an IEnumerable is a List, it may be more efficient to check for this and simply return the same List.
The worry is that sometimes you may want to be absolutely sure that the returned List is != to the original List. Because Microsoft doesn't document that ToList will return a new List, we can't be sure (unless someone found that documentation). It could also change in the future, even if it works now.
new List(IEnumerable enumerablestuff) is guaranteed to return a new List. I would use this instead.

Categories