C# Xamarin: ObservableCollections<Item> resets a variable to 0? - c#

I have in C# Xamarin an observablecollections with the class "Item" serving for the instances in the stack. In the class are a couple of variables, the most relevant ones being "Name" (string) and "id". Both of these are "public get private set".
When a user would rearrange the order of the stack (e.g. moving items up or down the stack) the string never changes and stays what it should be, but the ID, which except for in the constructor is never changed, somehow gets reset to 0.
foreach(Item it in ItemList.videoList) {
Post += string.Format("&order[]={0}",(it.id == 0) ? it.Name : vid.id.ToString() );
await DisplayAlert("Data", string.Format("{0} - {1}", it.Name, it.id), "OK");
}
(some items do not have a name - the ID is used instead)
If I add only 1 item and don't rearrange it, I get the following result in the display alert:
Tomato - 24
If I did add other items or rearrange the stack, this is the result:
Tomato - 0
Do note; the items themselves NEVER EVER change, so do the ID's! There isn't a hidden "foreach set all id's to 0" anywhere. (also note its a private get-only variable)
For extra reference, here are the rearranging functions. They take place in the Item class, and the list itself is a static instance within the class:
public static void BindButton(TapGestureRecognizer up, TapGestureRecognizer down, TapGestureRecognizer remove) {
Item item = ItemLastAdded;
up.Tapped += (e, i) => {
MoveUp(item);
};
down.Tapped += (e, i) => {
MoveDown(item);
};
remove.Tapped += (e, i) => {
ItemList.Remove(item);
};
}
private static void MoveUp(Item item) {
int indexOf = ItemList.IndexOf(item);
if (indexOf >= 1)) {
ItemList.Move(indexOf, indexOf - 1);
}
}
private static void MoveDown(Item item) {
int indexOf = videoList.IndexOf(item);
if (indexOf < videoList.Count - 1)) {
ItemList.Move(indexOf, indexOf + 1);
}
}

Related

How to replicate the changes from INotifyCollectionChanged on another collection

I have 2 ObservableCollection<T> objects. Let's call them A and B. I want to replicate the changes broadcasted by A (via INotifyCollectionChanged) to B.
In other words, when A changes, B must replicate the same change:
When A added an item, B must add the same item.
When A moved an item, B must do the same move.
And so on...
The problem comes with the complexity of NotifyCollectionChangedEventArgs.
I'd like to avoid writing the code that checks for all operation combinations.
(add + remove + reset + move + replace) x (single-item + multi-item) - (invalid combinations)
My assumption (and hope) is that this logic already exists in .Net (I'm on .Net 6).
Here's some code that demonstrates my vision.
ObservableCollection<int> A = new ObservableCollection<int>();
ObservableCollection<int> B = new ObservableCollection<int>();
A.CollectionChanged += (s, args) =>
{
// This line doesn't build. It's just to show my intent.
B.Apply(args);
};
A.Add(1);
A.Add(2);
// At this point B should contain 1 and 2 because they were added to A.
Is there an existing .Net solution for this problem?
If the recipe doesn't exist, any pointers on how to properly implement it are appreciated.
I'm not entirely sure what you are trying to achieve - but if A and B are always going to be equivalent, why not just find an abstraction that allows the use of A and remove the B collection? But if B is going to be modified independently of A then the operations - such as move - won't work in B given the difference in collections and indices.
If there is no possibility of removing one instance of the collection then you could always write a class that makes the code of your handler simpler.
var A = new ObservableCollection<int>();
var B = new ObservableCollection<int>();
var evts = new ObservableCollectionEvents<int>(A);
evts.Added += (i, x) => B.Insert(i, x);
evts.Removed += (i, x) => B.RemoveAt(i);
A.Add(1);
A.Add(2);
Console.WriteLine(string.Join(", ", B));
class ObservableCollectionEvents<T>
{
public event Action<int, T>? Added;
public event Action<int, T>? Removed;
public ObservableCollectionEvents(ObservableCollection<T> collection)
{
collection.CollectionChanged += OnCollectionChanged;
}
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
for (var i = e.NewStartingIndex; i < e.NewStartingIndex + e.NewItems!.Count; ++i)
Added?.Invoke(i, (T)e.NewItems[i - e.NewStartingIndex]!);
break;
case NotifyCollectionChangedAction.Remove:
for (var i = e.OldStartingIndex; i < e.OldStartingIndex + e.OldItems!.Count; ++i)
Removed?.Invoke(i, (T)e.OldItems[i - e.OldStartingIndex]!);
break;
// ...
}
}
}
This would also allow you to simplify the amount of operations you need to support. A replace could be modelled with a remove followed by an add at the same index - with a similar process for the move operations.

How do I delete items from a List<T> while enumerating the list using multiple threads?

I have an odd scenario in which I must enumerate a list on multiple threads, and the methods executing on those threads must be able to delete items from the list. Yes, I understand the design issues that reflects, but it's what I need to do.
I can't delete items as I go because that results in this exception:
Collection was modified; enumeration operation may not execute.
If this was single-threaded I would solve the problem like this:
for (var i = list.Count - 1; i >= 0; i--)
{
// depending on some condition:
list.RemoveAt(i);
}
How could I solve this problem when multiple threads are enumerating the list?
As stated, it would be better to solve this problem some other way, such as separating the list into separate lists. But for that weird, one-off scenario:
Instead of deleting items from the list as it's processed by multiple threads, mark them for deletion. Then, when all of the methods are done processing the list, create a new list out of the items that aren't marked for deletion.
One way to accomplish that without adding a property to an existing class is to create a new class and wrap instances of the existing class in the new one, like this:
public class Deletable<T>
{
public Deletable(T value)
{
Value = value;
}
public T Value { get; }
public bool Delete { get; private set; }
public void MarkForDeletion() => Delete = true;
}
Then you can use a few extensions to convert your List<T> into IEnumerable<Deletable<T>> and then filter out the "deleted" items when all of your threads are done:
public static class DeletableExtensions
{
public static Deletable<T>[] AsDeleteable<T>(this IEnumerable<T> source)
{
return source.Select(item => new Deletable<T>(item)).ToArray();
}
public static IEnumerable<T> FilterDeleted<T>(this IEnumerable<Deletable<T>> source)
{
return source.Where(item => !item.Delete).Select(item => item.Value);
}
}
Instead of passing List<Foo> to method, do this:
var deletables = fooList.AsDeleteable();
The result is a Deletable<Foo>[] which you can pass to other methods. It's not even a list, so those methods can't delete items from it. Instead they would call
item.MarkForDeletion();
When all of your threads are done processing, you would create a new, filtered result of the items that aren't deleted by calling:
var filtered = deletables.FilterDeleted();
This allows for the possibility that different threads are marking items for deletion for different reasons. In other words, they're not all doing exactly the same thing. If each thread is performing the exact same inspection then this should be handled using Parallel.ForEach or just breaking up the list into smaller lists. I can't imagine a scenario in which multiple threads should be doing the same thing with the same list.
Probably the most reliable way to do this would be to use a collection type built for being operated on from multiple threads. The System.Collections.Concurrent namespace has several classes for this. While these classes support adding items from multiple threads, most do not allow you to remove a specific item from the collection; rather you can only TryTake the next available item.
However, the ConcurrentDictionary class does give you the ability to remove specific items, since they each have a unique Key that identifies the item.
If it's acceptable to move your collection into a dictionary (simply give each item in the collection a unique key when adding it), then here's an example of how it could be done, with 5 different threads each iterating over the entire collection, and each removing items based on a different (but in many cases competing) condition:
private static ConcurrentDictionary<int, int> dict = new ConcurrentDictionary<int, int>();
private static void RemoveItems(Func<int, bool> condition)
{
int temp;
foreach (var item in dict)
{
if (condition.Invoke(item.Value)) dict.TryRemove(item.Key, out temp);
}
}
private static void Main()
{
Random r = new Random();
// Start with a list of random integers ranging in value from 1 to 100
var list = Enumerable.Range(0, 100).Select(x => r.Next(1, 101)).ToList();
// Add our items to a concurrent dictionary
for (int i = 0; i < list.Count; i++) dict.TryAdd(i, list[i]);
// Start 5 tasks where each one removes items based on a
// different (yet overlapping in many cases) condition
var tasks = new[]
{
Task.Factory.StartNew(() => RemoveItems(i => i % 15 == 0)),
Task.Factory.StartNew(() => RemoveItems(i => i % 10 == 0)),
Task.Factory.StartNew(() => RemoveItems(i => i % 5 == 0)),
Task.Factory.StartNew(() => RemoveItems(i => i % 3 == 0)),
Task.Factory.StartNew(() => RemoveItems(i => i % 2 == 0)),
};
// Wait for tasks
Task.WaitAll(tasks);
// Reassign our list to the remaining values
list = dict.Values.ToList();
GetKeyFromUser("\nDone! Press any key to exit...");
}
Here is an attempt for a direct solution to the problem, by making an enumerator than can be used concurrently by multiple threads, and allows removing elements from the list during the enumeration. Each element in the list is enumerated only once, so each thread processes only a subset of the list. I think that this arrangement complies better with the requirements of the question.
I couldn't make it with the standard enumerators because it is too risky to hold a lock from the call to MoveNext to the call of Current, so the enumerator is non-standard. It has a single method MoveNext, that also returns the current element as an out parameter. Actually it returns a wrapper of the current element, that includes a Remove() method that can be called to remove the element from the list.
public class ThreadSafeEnumerator<T>
{
private readonly IList<T> _list;
private readonly List<int> _indexes;
private readonly object _locker = new object();
private int _currentIndex;
public ThreadSafeEnumerator(IList<T> list)
{
_list = list;
_indexes = Enumerable.Range(0, list.Count).ToList();
_currentIndex = list.Count;
}
public bool MoveNext(out Removable current)
{
current = default;
T item;
int i;
lock (_locker)
{
_currentIndex--;
i = _currentIndex;
if (i < 0) return false;
item = _list[i];
}
current = new Removable(item, () =>
{
lock (_locker)
{
var index = _indexes.BinarySearch(i);
_indexes.RemoveAt(index);
_list.RemoveAt(index);
}
});
return true;
}
public struct Removable
{
public T Value { get; }
private readonly Action _action;
public Removable(T value, Action action)
{
Value = value;
_action = action;
}
public void Remove() => _action();
}
}
Usage example. A list of 10000 random numbers is processed in parallel by 4 threads, and all non-even numbers are removed.
Random random = new Random(0);
var list = Enumerable.Range(0, 10000).Select(_ => random.Next(0, 10000)).ToList();
var enumerator = new ThreadSafeEnumerator<int>(list);
var tasks = Enumerable.Range(0, 4).Select(_ => Task.Run(() =>
{
while (enumerator.MoveNext(out var current))
{
if (current.Value % 2 != 0) current.Remove();
}
})).ToArray();
Task.WaitAll(tasks);
Console.WriteLine($"Count: {list.Count}");
Console.WriteLine($"Top Ten: {String.Join(", ", list.OrderBy(n => n).Take(10))}");
Output:
Count: 5020
Top Ten: 2, 2, 6, 8, 10, 10, 12, 12, 16, 18

Classes with virtually common code

I have a number of custom collection classes. Each serves to provide a collection of various custom types - one custom type to one custom collection. The custom collections inherit List<T> [where T in this case is the specific custom type, rather then a generic] and provide some additional functionality.
I previously did away with the custom collections and had custom methods elsewhere, but I found as I extended the code that I needed the collections with their own methods.
It all works, everything is happy. But it irritates me, because I know I am not doing it properly. The issue is that each class uses pretty much the same code, varying only the type and a parameter, so I feel that it could be implemented as an abstract class, or generic, or extension to List, or ... but I'm not really understanding enough of the differences or how to go about it to be able to sort out what I need.
Here are two of my several collections, so that you get the idea:
// JourneyPatterns
public class JourneyPatterns : List<JourneyPattern>
{
private Dictionary<string, JourneyPattern> jpHashes; // This is a hash table for quick lookup of a JP based on its values
/* Add a journey pattern to the JourneyPatterns collection. Three methods for adding:
1. "Insert Before" (=at) a particular point in the list. This is the method used by all three methods.
2. "Insert After" a particular point in the list. This is "before" shifted by 1 e.g. "after 6" is "before 7"
3. "Append" to the end of the list. This is "before" with a value equal to the list count, and is the same as inherited "Add", but with checks
*/
public JourneyPattern InsertBefore(JourneyPattern JP, int before)
{
// check for a pre-existing JP with the same parameters (ignore ID). Do this by constructing a "key" based on the values to check against
// and looking it up in the private hash dictionary
JourneyPattern existingJP;
if (jpHashes.TryGetValue(JP.hash, out existingJP)) { return existingJP; }
else
{
// construct a new ID for this JP
if (string.IsNullOrWhiteSpace(JP.id)) JP.id = "JP_" + (Count + 1).ToString();
// next check that the ID specified isn't already being used by a different JPS
if (Exists(a => a.id == JP.id)) JP.id = "JP_" + (Count + 1).ToString();
// now do the add/insert
if (before < 0) { Insert(0, JP); } else if (before >= Count) { Add(JP); } else { Insert(before, JP); }
// finally add to the hash table for fast compare / lookup
jpHashes.Add(JP.hash, JP);
return JP;
}
}
public JourneyPattern InsertAfter(JourneyPattern JP, int after) { return InsertBefore(JP, after + 1); }
public JourneyPattern Append(JourneyPattern JP) { return InsertBefore(JP, Count); }
}
// JourneyPatternSections
public class JourneyPatternSections : List<JourneyPatternSection>
{
private Dictionary<string, JourneyPatternSection> jpsHashes; // This is a hash table for quick lookup of a JPS based on its values
/* Add a journey pattern section to the journeyPatternSections collection. Three methods for adding:
1. "Insert Before" (=at) a particular point in the list. This is the method used by all three methods.
2. "Insert After" a particular point in the list. This is "before" shifted by 1 e.g. "after 6" is "before 7"
3. "Append" to the end of the list. This is "before" with a value equal to the list count, and is the same as inherited "Add", but with checks
*/
public JourneyPatternSection InsertBefore(JourneyPatternSection JPS, int before)
{
// check for a pre-existing JPS with the same parameters (ignore ID). Do this by constructing a "key" based on the values to check against
// and looking it up in the private hash dictionary
JourneyPatternSection existingJPS;
if (jpsHashes.TryGetValue(JPS.hash, out existingJPS)) { return existingJPS; }
else
{
// construct a new ID for this JPS
if (string.IsNullOrWhiteSpace(JPS.id)) JPS.id = "JPS_" + (Count + 1).ToString();
// next check that the ID specified isn't already being used by a different JPS
if (Exists(a => a.id == JPS.id)) JPS.id = "JPS_" + (Count + 1).ToString();
// now do the add/insert
if (before < 0) { Insert(0, JPS); } else if (before >= Count) { Add(JPS); } else { Insert(before, JPS); }
// finally add to the hash table for fast compare / lookup
jpsHashes.Add(JPS.hash, JPS);
return JPS;
}
}
public JourneyPatternSection InsertAfter(JourneyPatternSection JPS, int after) { return InsertBefore(JPS, after + 1); }
public JourneyPatternSection Append(JourneyPatternSection JPS) { return InsertBefore(JPS, Count); }
}
As you can see, what is differing is the type (JourneyPattern, or JourneyPatternSection), and the prefix that I am using for the "id" property of the type ("JP_" or "JPS_"). Everything else is common, since the method of determining "uniqueness" (the property "hash") is part of the custom type.
Some of my custom collections require more involved and different implementations of these methods, which is fine, but this is the most common one and I have implemented it about 6 times so far which seems a) pointless, and b) harder to maintain.
Your thoughts and help appreciated!
Assming tha both JourneyPattern and JourneyPatternSection implements a common interface like:
public interface IJourney
{
string hash { get; set; }
string id { get; set; }
}
You can implements a base class for your collections:
public abstract class SpecializedList<T> : List<T> where T : class, IJourney
{
private Dictionary<string, T> jpHashes; // This is a hash table for quick lookup of a JP based on its values
protected abstract string IdPrefix { get; }
/* Add a journey pattern to the JourneyPatterns collection. Three methods for adding:
1. "Insert Before" (=at) a particular point in the list. This is the method used by all three methods.
2. "Insert After" a particular point in the list. This is "before" shifted by 1 e.g. "after 6" is "before 7"
3. "Append" to the end of the list. This is "before" with a value equal to the list count, and is the same as inherited "Add", but with checks
*/
public T InsertBefore(T JP, int before)
{
// check for a pre-existing JP with the same parameters (ignore ID). Do this by constructing a "key" based on the values to check against
// and looking it up in the private hash dictionary
T existingJP;
if (jpHashes.TryGetValue(JP.hash, out existingJP)) { return existingJP; }
else
{
// construct a new ID for this JP
if (string.IsNullOrWhiteSpace(JP.id)) JP.id = "JP_" + (Count + 1).ToString();
// next check that the ID specified isn't already being used by a different JPS
if (Exists(a => a.id == JP.id)) JP.id = IdPrefix + (Count + 1).ToString();
// now do the add/insert
if (before < 0) { Insert(0, JP); } else if (before >= Count) { Add(JP); } else { Insert(before, JP); }
// finally add to the hash table for fast compare / lookup
jpHashes.Add(JP.hash, JP);
return JP;
}
}
public T InsertAfter(T JP, int after) { return InsertBefore(JP, after + 1); }
public T Append(T JP) { return InsertBefore(JP, Count); }
}
Then implement each collection:
public class JourneyPatterns : SpecializedList<JourneyPattern>
{
protected override string IdPrefix => "JP_";
}
public class JourneyPatternSections : SpecializedList<JourneyPatternSection>
{
protected override string IdPrefix => "JPS_";
}

Get last item added in a BindlingList

I have a BindingList variable and I need to get the last item that was added to it. The following code works, but I'd like to know if there's a better way or something that I'm missing:
WorkoutScheduleList[WorkoutScheduleList.IndexOf(WorkoutScheduleList[(WorkoutScheduleList.Count - 1)])].WorkoutScheduleID);
It's not the easiest to read, but it basically takes the count of the list and subtracts 1 from it, and uses that for the IndexOf method to get the last item that was added to the list.
You can handle the ListChanged event of the binding list, and keep a reference of the item that was added.
Example:
class Program
{
static object last_item;
static void Main(string[] args)
{
BindingList<object> WorkoutScheduleList = new BindingList<object>();
WorkoutScheduleList.ListChanged += (s, e) => {
if (e.ListChangedType == ListChangedType.ItemAdded)
last_item = WorkoutScheduleList[e.NewIndex];
};
WorkoutScheduleList.Add("Foo");
WorkoutScheduleList.Add("Bar");
WorkoutScheduleList.Insert(1, "FooBar");
//prints FooBar
Console.WriteLine(String.Format("last item added: {0}", last_item));
}
}
var lastItem = WorkoutScheduleList[WorkoutScheduleList.Count - 1].WorkoutScheduleID;
or using LinQ
var lastLinqItem = WorkoutScheduleList.Last().WorkoutScheduleID;

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