How to replicate the changes from INotifyCollectionChanged on another collection - c#

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.

Related

How to create a list of methods?

How can I execute a method one by one in the order they were added to the List<Action>?
List<Action> actions = new List<Action>();
List<int> listaInt = new List<int>();
for (int i = 0; i < 5; i++) {
listaInt.Add(i);
var entries = entries1.Select(d => string.Format("{0}", string.Join(",", d)));
actions.Add(new Action(() => {
Console.WriteLine("counterList: " + string.Join(",", entries ));
}));
}
I want to execute every function, but with the list of integers(List<int>)
foreach (Action action in actions) {
action();
}
Result:
listaInt: 0,1,2,3,4
listaInt: 0,1,2,3,4
listaInt: 0,1,2,3,4
listaInt: 0,1,2,3,4
listaInt: 0,1,2,3,4
Expected result:
listaInt: 0
listaInt: 0,1
listaInt: 0,1,2
listaInt: 0,1,2,3
listaInt: 0,1,2,3,4
Your question isn't entirely clear. But it seems like what you want is for each element (i.e. "snapshot") in the list to represent the original List<int>'s state at the time it was added to the action list.
If so, then the main problem here is that you are adding the reference to the original list. Any changes to that list later will still be observed in any copy of that reference, such as those you are adding.
If you want an actual "snapshot", then you need to make one. I.e. make a copy of the list when you add it to the action list. For example:
public static void TakeSnapShot(List<int> listaInt, int i) {
GridSnapshotAction gridSnapshotAction = new GridSnapshotAction();
List<int> listCopy = new List<int>(listaInt);
gridSnapshotAction.AddAction(() => {
Console.WriteLine(i);
PrintLista(listCopy);
});
gridSnapshotActionList.Add(gridSnapshotAction);
}
There are other places in the code where you might make a copy. And there are other ways you could implement this. For example, if you were using ImmutableList<T>, then the original list reference would be the one getting updated, while each captured list would be the snapshot you want (and this approach would be somewhat more efficient, since ImmutableList<T> is optimized to share data between copies of the list when possible).
The bottom line is: if you want separate copies of the list object, you need to do something to make sure you have separate copies. Reference types like List<T> don't copy the contents of the object when you assign the reference; just the reference itself is copied, all copies of that reference will refer to the same object, and changes to that one object will be observed when using any copy of that reference.
If I understand your question correctly, the problem is by the time the actions are executed the list already contains 5 elements, therefore they will all print 5 items. If what you want is to print the items that were contained when the action was added to actions, then you can create the string with the desired output and then print that string. Because that string is in the closure, you'll be able to access it with the value you expect, i.e.
public static void PrintList()
{
var actions = new List<Action>();
var listaInt = new List<int>();
for (int i = 0; i < 5; i++)
{
listaInt.Add(i);
var entries = string.Join(",", listaInt.Select(d => string.Format("{0}", string.Join(",", d))));
actions.Add(() => Console.WriteLine("counterList: " + entries));
}
foreach (var action in actions)
{
action();
}
}

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

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);
}
}

How to iterate over list while removing items at the same time?

I'm trying to find an elegant way to iterate over a list while items are removed at the same time.I know this solution. But my conditions are something harder:
all single-threaded here
Iteration must be forward.
Every item must be processed exactly once.
Multiple and random items can be removed while 1 item is being processed.
Items are complex and smart objects. They execute a custom method and it can decide that some items (0 to all) shall be removed.
(add and insert can happen too, but just now this is not important, in case there is a way to handle this at the same time, that would be great)
Question: Is this possible ? If yes, how ?
I have the idea of marking the objects as removed / inactive. When I iterate again later, I will remove them without calling them to do things. The iteration will be repeated quite often, that's why every object must have exactly 1 turn at each iteration. Would that work ?
This is how I handle things now. It's not perfect but gives you the hint what is asked I hope.
Pseudo-code:
class Foo
{
public void DoStuff()
{
// do other stuff
if (condition)
Kill(x); // should result in list.RemoveAt(x) somehow
}
}
class Program
{
[STAThread]
static void Main(string[] args)
{
List<Foo> list = new List<Foo>();
for (int i = 0; i < 15; i++)
list.Add(new Foo());
for (int i = 0; i < list.Count; i++)
list[i].DoStuff();
Console.ReadKey();
}
}
(This is not an XY Problem. I'm sure. I have this sitting on my mind for years now and I decided to finally find a solid solution. I'm working in C# for this. This is not a prank. I'm sorry if it seams like it.)
Thanks for any help!
What you can do is use an ObservableCollection here so that the code that is iterating over the collection has a way of detecting when and how the collection is mutated while it is iterating. By using an ObservableCollection the iterating code can increment the index when an item is added before the current index, or decriment it when an item is removed from before the current index.
public static IEnumerable<T> IterateWhileMutating<T>(
this ObservableCollection<T> list)
{
int i = 0;
NotifyCollectionChangedEventHandler handler = (_, args) =>
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (args.NewStartingIndex <= i)
i++;
break;
case NotifyCollectionChangedAction.Move:
if (args.NewStartingIndex <= i)
i++;
if (args.OldStartingIndex <= i) //note *not* else if
i--;
break;
case NotifyCollectionChangedAction.Remove:
if (args.OldStartingIndex <= i)
i--;
break;
case NotifyCollectionChangedAction.Reset:
i = int.MaxValue;//end the sequence
break;
default:
//do nothing
break;
}
};
try
{
list.CollectionChanged += handler;
for (i = 0; i < list.Count; i++)
{
yield return list[i];
}
}
finally
{
list.CollectionChanged -= handler;
}
}
The code is taken from this other answer of mine. It contains additional tangential information about the consequences of iterating a sequence while mutating it, as well as some additional explanation about this code and the implications of its design decisions.
I have the idea of marking the objects as removed / inactive.
Yes, I think something like this is a reasonable approach. What I would do is to first collect all the items to remove and then remove them all at once. In code, it could look something like:
var toRemove = new HashSet<Item>();
foreach (var item in list)
{
toRemove.UnionWith(item.GetItemsToRemove());
}
list.RemoveAll(item => toRemove.Contains(item));
The nice thing about this approach is that it should be fast (O(n)), because while removing a single item from a List<T> is O(n), removing multiple items from it at the same time is also O(n).

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;

BindingList projection wrapper

Is there a simple way to create a BindingList wrapper (with projection), which would update as the original list updates?
For example, let's say I have a mutable list of numbers, and I want to represent them as hex strings in a ComboBox. Using this wrapper I could do something like this:
BindingList<int> numbers = data.GetNumbers();
comboBox.DataSource = Project(numbers, i => string.Format("{0:x}", i));
I could wrap the list into a new BindingList, handle all source events, update the list and fire these events again, but I feel that there is a simpler way already.
I have just stumbled across this question and I realized I might post the code I ended up with.
Since I wanted a quick solution, I made a sort of a poor man's implementation. It works as a wrapper around an existing source list, but it creates a full projected list of items and updates it as needed. At first I hoped I could do the projection on the fly, as the items are accessed, but that would require implementing the entire IBindingList interface from scratch.
What is does: any updates to the source list will also update the target list, so bound controls will be properly updated.
What it does not do: it does not update the source list when the target list changes. That would require an inverted projection function and I didn't need that functionality anyway. So items must always be added, changed or removed in the source list.
Usage example follows. let's say we have a list of numbers, but we want to display their squared values in a data grid:
// simple list of numbers
List<int> numbers = new List<int>(new[] { 1, 2, 3, 4, 5 });
// wrap it in a binding list
BindingList<int> sourceList = new BindingList<int>(numbers);
// project each item to a squared item
BindingList<int> squaredList = new ProjectedBindingList<int, int>
(sourceList, i => i*i);
// whenever the source list is changed, target list will change
sourceList.Add(6);
Debug.Assert(squaredList[5] == 36);
And here is the source code:
public class ProjectedBindingList<Tsrc, Tdest>
: BindingList<Tdest>
{
private readonly BindingList<Tsrc> _src;
private readonly Func<Tsrc, Tdest> _projection;
public ProjectedBindingList(
BindingList<Tsrc> source,
Func<Tsrc, Tdest> projection)
{
_projection = projection;
_src = source;
RecreateList();
_src.ListChanged += new ListChangedEventHandler(_src_ListChanged);
}
private void RecreateList()
{
RaiseListChangedEvents = false;
Clear();
foreach (Tsrc item in _src)
this.Add(_projection(item));
RaiseListChangedEvents = true;
}
void _src_ListChanged(object sender, ListChangedEventArgs e)
{
switch (e.ListChangedType)
{
case ListChangedType.ItemAdded:
this.InsertItem(e.NewIndex, Proj(e.NewIndex));
break;
case ListChangedType.ItemChanged:
this.Items[e.NewIndex] = Proj(e.NewIndex);
break;
case ListChangedType.ItemDeleted:
this.RemoveAt(e.NewIndex);
break;
case ListChangedType.ItemMoved:
Tdest movedItem = this[e.OldIndex];
this.RemoveAt(e.OldIndex);
this.InsertItem(e.NewIndex, movedItem);
break;
case ListChangedType.Reset:
// regenerate list
RecreateList();
OnListChanged(e);
break;
default:
OnListChanged(e);
break;
}
}
Tdest Proj(int index)
{
return _projection(_src[index]);
}
}
I hope someone will find this useful.

Categories