Update list without modifying the parent - c#

I have an in memory list, from which I get some rows and update a field. I can't understand why the parent list is also updated. I used ToList(), Range(), new List<type>(filteredRows), nothing works. I don't want to create another list and just push item by item, because the parent is huge and can affect the performance.
private readonly IServiceScopeFactory scopeFactory;
private List<Row> rows= new List<Row>();
public InMemoryData(IServiceScopeFactory scopeFactory)
{
this.scopeFactory = scopeFactory;
}
the original rows looks like below:
{ Name: 'Team A', Position: 1, CountryId: 1},
{ Name: 'Team B', Position: 2, CountryId: 2},
{ Name: 'Team C', Position: 3, CountryId: 1},
{ Name: 'Team D', Position: 4, CountryId: 1}
The problem is in the following method:
public List<Row> GetFiltereRows(int? countryId = null) {
if (countryId != null) {
var filteredRows = rows
.Where(x => x.CountryId == (int)countryId)
.ToList();
var position = 0;
return filteredRows.Select(x => {
// here, also the entity "x" in the parent list is updated
x.Position = ++position;
return x;
}).ToList();
}
return rows;
}
When I filter the data by CountryId 1, I want that Team C to have Position 2 and Team D to have Position 3 (to recalculate positions only for selected country).
I think I need to set the rows list as readonly, or to create clones from it.
I can't understand why for another case, the clone works:
var filteredRows = rows;
if (countryId != null) {
filteredRows = filteredRows.Where(x => x.CountryId == countryId ).ToList();
}
here, even if the filteredRows is declared as rows, this will not update the original list, filtering it. I know something about immutability from another languages, due of that I'm thinking that I need to do theoriginal list immutable, to use only copies of it in another methods. But also, somethimes I need to refresh the original list, so should still be able to update it (or recreate).

I used ToList(), Range(), new List(filteredRows), nothing works.
new List<Type>(filteredRows) will create another reference pointing to the same list, it won't create a new list and clone the items in the original (parent) list.
.ToList()? Same thing, Actually, take a look at .ToList()'s implementation:
public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) => source != null ? new List<TSource>(source) : throw Error.ArgumentNull(nameof (source));
It's just the same as new List<Type>(filteredRows), but throws an exception if filteredRows is null!
"// here, also the entity "x" in the parent list is updated" this is normal
As stated, you are not deep-cloning the original items, so any modification to the filtered item is a direct modification to the original item.
I can think of two choices to overcome this issue:
After filtering rows by countryId, you can tell how long is filteredRows, so maybe it won't affect the performance if you deep-clone it
return filteredRows.Select(x => {
var xClone = new Row(x); // deep clone
xClone.Position = ++position;
return xClone;
}).ToList();
Or, you would write new function to reverse the edits made to the original list at some point after using the returned value from GetFiltereRows..
return filteredRows.Select(x => {
x.OriginalPosition = x.Position;
x.Position = ++position;
return x;
}).ToList();
Usage:
var filteredRows = GetFiltereRows(rows);
// use the filteredRows
RefreshRows();
void RefreshRows(){
foreach(var item in rows) {
item.Position = item.OriginalPosition;
item.OriginalPosition = -1;
}
}

Even if you were to create a new list and push item by item, you would have the same issue.
This is because each row (your object with Name, Position, and CountryId) is a reference type as well. So even if you make a brand new list, that new list will still point to the same object in the other list. This is why you see both lists being updated, even after you successfully made a new list.
Your code would work as you intended if your list was composed of a value type, such as List<int>.
The only way I know how to fix this is to make a Row class and have it conform to ICloneable, like this:
class Row: ICloneable
{
public String Name;
public int Position;
public int CountryId;
public object Clone()
{
return this.MemberwiseClone();
}
}
Unfortunately, you would have to call Clone() on each row in your list, which will affect performance.
If anyone else has a better way, feel free to add on.

Related

C# - How to replace a list or property values within a List<T>?

I have binded a control with a BindingList dataSource. Let's say:
var dataSource = new List<Model>()
{
new Model()
{
Name = "A",
CostValue = 60
},
new Model()
{
Name = "B",
CostValue = 0
}
};
And whenever there is a property change, it will trigger a third party method that recalculates the CostValue and returns a new list of CostValues like:
var newCostValue = new List<double>(){40,0};
So what is the best way to replace the CostValue list within the dataSource bindinglist with the new CostValue, since there might be lots of models inside the dataSource bindinglist, with two foreach loop would be really slow.
I have tried to find the difference between the two lists, but with no luck. It seems that by getting the costValue using SELECT, the Except function just don't work, why is that?
var list11 = dataSource.Select(x => x.CostValue).ToList();//If we create a new list with just values, the Except would work
var newCostValue = new List<double>() {60,0};
var dif = list11.Except(newCostValue).ToList();//Getting None, why is that?
Thanks in advance!
You need to implement IEquatable, so the items can be compared for equality.
This might do the trick for you if you are also getting the name.
dataSource.First(d => d.Name == "B").CostValue = newCostValue[1];
but another one could be
dataSource.First(d => d.CostValue == newCostValue[0]).CostValue = newCostValue[1];
I would like to offer a better answer which envolve more thread safety here.
Since items can be deleted, I assume, and assuming that the field Name is mapped uniquly to a price:
public class Model
{
public string Name;
public double CostValue;
}
public List<Model> dataSource = new List<Model>()
{
new Model()
{
Name = "A",
CostValue = 60
},
new Model()
{
Name = "B",
CostValue = 0
}
};
/// <summary>
/// Updates the original dataSource with the new prices
/// </summary>
/// <param name="newPrices">Dictionary whereas the Keys are names and values are CostValue of an item</param>
public void PriceChange(Dictionary<string, double> newPrices)
{
foreach (Model model in dataSource)
{
if (newPrices.ContainsKey(model.Name))
model.CostValue = newPrices[model.Name];
}
}
Thus, the update will cost an O(n) run time whereas n is the number of items in dataSource and of course is more safe to rely on the item name itself rather than its index in the list.
Why?
Because if you have another thread deleting items or modifies them so their index has changed, you gonna have a serious problem, you will update different items from what you wanted.
All you have to change is to return a new price dictionary rather than a List<double>

Group, Sort, and then Merge to a Observable Collection?

I have a List of Items that have a "DisplayOrder" property. This can either have NULL or int value. If it has int value, it has priority and should be in the 1st group of the Observable Collection. Items in the 1st group are also sorted by DisplayOrder.
If it is NULL, then it belongs to the 2nd group, sorted alphabetically.
The 1st group and 2nd group are then combined for a Main Items Collection Observable Collection which I bind to a ListView.
This is my current code though I am worried if there is a much optimal way of doing it.
var MainItemCollection = new ObservableCollection<MainItemViewModel>();
var ItemsWithProperty = new ObservableCollection<MainItemViewModel>();
var ItemsWithNullProperty = new ObservableCollection<MainItemViewModel>();
foreach (var item in DataObject.MainItems)
{
if (item.DisplayOrder == null)
ItemsWithNullProperty.Add(new MainItemViewModel(item));
else
ItemsWithProperty.Add(new MainItemViewModel(item));
}
ItemsWithProperty = new ObservableCollection<MainItemViewModel>(ItemsWithProperty.OrderBy(c => c.DisplayOrder));
ItemsWithNullProperty = new ObservableCollection<MainItemViewModel>(ItemsWithNullProperty.OrderBy(c => c.Title));
//Add those with priorities first sorted by DisplayOrder 1,2,3,4
foreach (var c in ItemsWithProperty)
{
MainItemCollection.Add(c);
}
//Add those without priority sorted Alphabetically
foreach (var c in ItemsWithNullProperty)
{
MainItemCollection.Add(c);
}
Thank you!
Get the items with DisplayOrder=null & order them by Title:
ItemsWithNullProperty=DataObject.MainItems.Where(x=>x.DisplayOrder==null).OrderBy(o=>o.Title).ToList();
Get the items with DisplayOrder(all items except the above query) & order them by DisplayOrder:
ItemsWithProperty= DataObject.MainItems.Except(ItemsWithNullProperty).OrderBy(o=>o.DisplayOrder).ToList();
Fill the data in MainCollection:
var allItems = MainItemCollection.Concat(ItemsWithProperty)
.Concat(ItemsWithNullProperty)
.ToList();
When doing things like this, you don't need all those intermediate ObservableCollections - you can use the appropriate data structures like array, list, dictionary, hash set etc. or Linq queries. In this particular case, the whole procedure can be reduced to something like this
var MainItemCollection = new ObservableCollection<MainItemViewModel>(DataObject.MainItems
.OrderBy(item => item.DisplayOrder ?? int.MaxValue)
.ThenBy(item => item.DisplayOrder == null ? item.Title : string.Empty)
);
I feel that this is a pretty common scenario.
There is a sorted ObservableCollection bound to some XAML UI, and once more data is available UI needs to be updated without full refresh.
Whenever new ObservableCollection is created like in suggestions above, all items will be rebound and therefore UI fully updated.
I'm surprised that there are no library methods to achieve this. Here is the solution I've came up with. Hope someone might find it useful.
public static class ObservableCollectionExtensions
{
public static void MergeSortedListIntoSortedObservableCollection<T>(this ObservableCollection<T> destination, IList<T> list, Func<T, T, int> compareFunc)
{
int dest_index = 0;
int list_index = 0;
while (list_index < list.Count)
{
if (dest_index >= destination.Count)
{
destination.Add(list[list_index]);
list_index++;
dest_index++;
}
else
{
if (compareFunc(destination[dest_index], list[list_index]) > 0)
{
destination.Insert(dest_index, list[list_index]);
list_index++;
dest_index++;
}
else
{
dest_index++;
}
}
}
}
}

Filter list to remove non numeric items

I am using the following controller method to grab the id's of all selected items in my Grid.Mvc, however I am noticing I am getting not only the id's of the selected items, but also a "false" for each row, which obviously I don't want.
I have put together the following code smell to leave me with just the id's so that I can do my thing however is there a better way of doing this, at the very least is there a way of filtering out the non numeric list items rather than my existing method which is hardcoding true/false:
//POST
[HttpPost]
[ActionName("Index")]
public ActionResult IndexPost(FormCollection collection)
{
var selectedIds = collection.GetValues("selectedAddressesIds");
List<string> myList = new List<string>();
myList = selectedIds.ToList();
myList.RemoveAll(x => (x == "false") || (x == "true"));
if (myList != null )
{
foreach (var id in myList)
{
// do what you want with the selected id.
}
}
return View();
}
To answer just the question about filtering, you could retrieve just the numeric values using one of these options:
var exampleList = new List<string>{"1", "2", "rubbish", "3", "trash", "4.5"};
int dummyInt; // just because you need an out param of type int
// filteredInts will contain {1, 2, 3}
var filteredInts = exampleList.Where(item => int.TryParse(item, out dummyInt));
double dummyDouble; // just because you need an out param of type double
// filteredDoubles will contain {1, 2, 3, 4.5}
var filterdDoubles = exampleList.Where(item => double.TryParse(item, out dummyDouble));
Ofcourse, if you just want to filter the original list, you can use the complementary logic for RemoveAll():
// If you only want int values:
myList.RemoveAll(item => ! int.TryParse(item, out dummyDouble));
// ..or want to also keep double values:
myList.RemoveAll(item => ! double.TryParse(item, out dummyDouble));
If the GetValues("selectedAddressesIds") method is returning things other than the IDs of the selected addresses, that is the problem that you should fix rather than tweaking the results list.
Without seeing where the results come from I'm not sure why you have values of "true" and "false" in this list, but if it is for every entry then it shouldn't be too hard to find if you look at where the selectedAddressesIds collection is populated.
To answer the title question more directly, though, if you merely want to avoid hard-coding "true" and "false" you can try to parse to int and discard any that don't parse:
List<string> myList = new List<string>();
int dummy; // because TryParse takes an out parameter
myList.RemoveAll(x => !int.TryParse(x, out dummy));
But, I'd encourage you to find the root cause of the invalid values.

Assigning to List to another List previous list automatically changes

I have two lists. When I assign List1 to List2 and I update List1, List2 automatically updates too. List2 shouldn't update. Why does this happen?
Here is my code:
public List<TrialBalance> TBal { get; set; }
public List<TrialBalance> PrevTBal { get; private set; }
if (this.PrevTBal == null)
{
this.PrevTBal = this.TBal;
}
for (int x = 0; x < this.TBal.Count; x++)
{
this.TBal[x].Balance = this.TBal[x].Balance + adjustments;
}
You are only assigning the references, not creating a copy of either the list or the items in the list.
You should create a new list and add all the items to it.
this.PrevTBal = new List<TrialBalance>(this.TBal.Select(b => clone(b));
When you assign a List<T>, you're copying the handle to the actual list in memory, which means the same list instance is being referenced by both variables.
In order to avoid this, you'd need to clone the list itself. In this case, that likely means needing to do two things - first, make a way to clone TrialBalance, then clone the list too:
// This assumes a TrialBalance.Clone() method which returns a new TrialBalance copy
this.PrevTBal = this.TBal.Select(tb => tb.Clone()).ToList();
Replace
if (this.PrevTBal == null)
{
this.PrevTBal = this.TBal;
}
by:
if (this.PrevTBal == null)
{
this.PrevTBal = this.TBal.ToList();
}
This way you're actually creating a copy of it instead of just refering it.

How do I update a single item in an ObservableCollection class?

How do I update a single item in an ObservableCollection class?
I know how to do an Add. And I know how to search the ObservableCollection one item at a time in a "for" loop (using Count as a representation of the amout of items) but how do I chage an existing item. If I do a "foreach" and find which item needs updating, how to I put that back into the ObservableCollection>
You can't generally change a collection that you're iterating through (with foreach). The way around this is to not be iterating through it when you change it, of course. (x.Id == myId and the LINQ FirstOrDefault are placeholders for your criteria/search, the important part is that you've got the object and/or index of the object)
for (int i = 0; i < theCollection.Count; i++) {
if (theCollection[i].Id == myId)
theCollection[i] = newObject;
}
Or
var found = theCollection.FirstOrDefault(x=>x.Id == myId);
int i = theCollection.IndexOf(found);
theCollection[i] = newObject;
Or
var found = theCollection.FirstOrDefault(x=>x.Id == myId);
theCollection.Remove(found);
theCollection.Add(newObject);
Or
var found = theCollection.FirstOrDefault(x=>x.Id == myId);
found.SomeProperty = newValue;
If the last example will do, and what you really need to know is how to make things watching your ObservableCollection be aware of the change, you should implement INotifyPropertyChanged on the object's class and be sure to raise PropertyChanged when the property you're changing changes (ideally it should be implemented on all public properties if you have the interface, but functionally of course it really only matters for ones you'll be updating).
You don't need to remove item, change, then add. You can simply use LINQ FirstOrDefault method to find necessary item using appropriate predicate and change it properties, e.g.:
var item = list.FirstOrDefault(i => i.Name == "John");
if (item != null)
{
item.LastName = "Smith";
}
Removing or adding item to ObservableCollection will generate CollectionChanged event.
Here are Tim S's examples as extension methods on top of the Collection Class:
CS with FirstOrDefault
public static void ReplaceItem<T>(this Collection<T> col, Func<T, bool> match, T newItem)
{
var oldItem = col.FirstOrDefault(i => match(i));
var oldIndex = col.IndexOf(oldItem);
col[oldIndex] = newItem;
}
CS with Indexed Loop
public static void ReplaceItem<T>(this Collection<T> col, Func<T, bool> match, T newItem)
{
for (int i = 0; i <= col.Count - 1; i++)
{
if (match(col[i]))
{
col[i] = newItem;
break;
}
}
}
Usage
Imagine you have this class setup
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
You can call either of the following functions/implementations like this where the match parameter is used to identify the item you'd like to replace:
var people = new Collection<Person>
{
new Person() { Id = 1, Name = "Kyle"},
new Person() { Id = 2, Name = "Mit"}
};
people.ReplaceItem(x => x.Id == 2, new Person() { Id = 3, Name = "New Person" });
VB with Indexed Loop
<Extension()>
Public Sub ReplaceItem(Of T)(col As Collection(Of T), match As Func(Of T, Boolean), newItem As T)
For i = 0 To col.Count - 1
If match(col(i)) Then
col(i) = newItem
Exit For
End If
Next
End Sub
VB with FirstOrDefault
<Extension()>
Public Sub ReplaceItem(Of T)(col As Collection(Of T), match As Func(Of T, Boolean), newItem As T)
Dim oldItem = col.FirstOrDefault(Function(i) match(i))
Dim oldIndex = col.IndexOf(oldItem)
col(oldIndex) = newItem
End Sub
This depends on what type of object it is.
If it is an ordinary C# class, just change the object's properties. You don't have to do anything to the collection. The collection holds a reference to the object which is the same even if the object's properties changes. A change on the object won't trigger the change notification for the collection itself, as the collection has not really change, just one of the objects in it.
If it is an immutable C# class (such as string), a struct or another value type you have to remove the old value and add the new one.

Categories