I am building a control in xamarin forms that binds to a list of objects. To get this binding to work I need to use observable collections (otherwise propertychanged methods don't fire).
I noticed a really frustrating interaction as a result of needing to use OC's as opposed to lists. Whenever the binded OC updates, the values in my controls are automatically updated, even if they are just references of the OC, Here is how i am copying the OC.
//Internal list of events
private List<EventItem> _events;
void OnEventsChanged(ObservableCollection<EventItem> eventsCollection)
{
//Error handle
List<EventItem> events = eventsCollection.ToList();
//Do something
_events = events;
}
The problem comes when the OC updates, I want to check for new/deleted AND altered objects. The issue is that when the OC updates, it is updating the internal list (_events) aswell. This means when I go to do comparisons between the old & new values, they are the same.
Honestly I don't really understand how c# handles copying references of objects around, I had a similar issue a while back with DateTime.Now being calculated as opposed to copying the value of the already initialised object.
var time = DateTime.Now;
await Task.Delay(1000);
var time2 = time; //This is 1 second later than time, not the value of time (which is what I wanted)
I have used Objective-C in the past and that has the concept of MutableCopy where you can assign a new list from an existing one, they have the same values but aren't linked.
How can I do this in C# so that my controls internal list is only updated by me and not the OC?
Thanks
That's perfectly normal. If I have enough time, I'll try to explain it to you.
In a nutshell, the observableList (or a List) is a list of reference to the objects and not a list of objects. The thing is that the objects are not copied inside a list but the list contains a reference to the different objects. That means that if you do something like ToList(), you get another list of references to the exact same objects.
Now to solve your problem. Just create a new list with new objects with something like
var newList = oldList.Select(x => new Model(x)).ToList();
And of course the Model class has a constructor that accept a Model as a parameter and copy the properties.
When you write _events = events;, you create not a new object, but a reference for the same object. https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/index .
You should to clone (create a copy of object itself) as it mentioned in comment by #Matt.
Related
maybe its been a long day, but I just cant figure this out.
I retrieve a large custom object from WCF, and store it in an application variable.
This happens every 20 minutes.
For each web user, I am checking the existence and timeout of this application variable, and if needed, re-query my wcf and build a new object, and re-store it in the application variable.
This all good and well.
Now, I am trying to "make a copy" of this "master" object, modify it, and store it in a session variable, modifying it as needed throughout the session life cycle. (modifying the session variable).
Everytime I modify the session object, the object in the application variable gets modified.
Pseudo
application("mastervar") = object from wcf (obejct type - xcustomclass)
dim mynewobject as new xcustomclass
mynewobject = application("mastervar")
* Modifying mynewobject, also modifies application("mastervar")
I have tried:
session("mynewSessionVar") = application("mastervar")
mynewobject = session("mynewSessionVar")
Modifying mynewobject, modifies application("mastervar")
I have tried:
Manually copying all properties in mastervar object to new object, with a for loop.
mycustomobject = new xcustomclass
mycustomobjectObject as new xcustomclass.object
mymasterobject = application("mastervar")
for each object in mymasterobject.objectslist
mycustomobjectObject = new xcustomclass.object
with mycustomobjectObject
.property = object.property
end with
mycustomobject.objectlist.add(mycustomobjectObject)
next
Same thing, modifying mycustomobject, also modifies application("mastervar")
As I said, maybe its been a long day, but I've been bumping my head against this for hours...
EDIT
Private Function copy_fresh_units(unitsFromWcf As WebResortUnits) As WebResortUnits
Dim myFreshUnits As New WebResortUnits
Dim myFreshUnit As WebResortUnits.qbunit
For Each Unit In unitsFromWcf.resortUnits
myFreshUnit = New WebResortUnits.qbunit
With myFreshUnit
' .Availability = Unit.Availability
.mapDetails = Unit.mapDetails
End With
myFreshUnits.resortUnits.Add(Unit)
Next
return myFreshUnits
End Function
Modifying the availability property in myfreshUnits, it still updates the app var. I have had a look at reference and value types, and it is definitely my issue. But taking this alst edit into account, I know I am missing something, what it is, I am not sure... :-)
You are creating a new reference to the objects and then modifying the object that you reference and expecting your two references to not be the same object?
It sounds like what you really want to do is to clone the objects or make a deepcopy of the objects.
If these are custom objects you will need custom code to make a Clone of them.
When you Clone your object be sure that you create a new object and set all of the value types on that new object from your old object. Then go through and clone all of your reference types and set the reference properties on your clone to point to the clones of the properties you've created.
EDIT: To address your update
The problem is still... everything you are copying is obviously a reference type and it's not being cloned. So there is only one object in existence therefor when you edit either reference it changes the object.
EDIT 2: Serialization will help you
Serializing and deserializing your base object in memory is an easy way to clone it. I generally write a custom clone method that serializes / deserializes the object. That way you have a Clone() method that you will always call and any custom code you need that doesn't get handled properly with serialize / deserialize you can handle in that method.
My guess is that the properties you are copying are not all primitives themselves (int, float, string, etc). When you then alter the corresponding property on the new object, it is then altering the original property (since it's not a primitive and is really an object reference).
Check out https://github.com/JesseBuesking/BB.DeepCopy which is probably a bit of overkill, but if you read what's under The problem this addresses you can see alternative approaches for helping in this situation.
I have an object that represents a client, and that object has a list of the clients branches:
private List<Branch> _branches;
[System.Xml.Serialization.XmlArray("Branches"), System.Xml.Serialization.XmlArrayItem(typeof(Branch))]
public List<Branch> Branches
{
get { return _branches; }
set
{
_branches = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("Branches"));
}
}
}
In one form (WinForms) I have a ComboBox that I've bound to that list:
// creating a binding list for the branches
var bindingList = new BindingList<Branch>(Client.Branches);
// bind list to combo box
cmbBranches.DataSource = bindingList;
cmbBranches.DisplayMember = "Name";
cmbBranches.ValueMember = "Name";
In another function, I create a new Branch object and add it to the existing list: Client.Branches.Add(newBranch). I would expect this to update the ComboBox but it doesn't. Why not, and how do I make it update? (Edit: I'd also like this to update when removing an object from the list. The reason it doesn't work is, I assume, directly related to why the box isn't updating when Add is called.)
In doing research, I found this SO answer, which seems to imply that it will work. I feel like I'm missing something simple ...
difference between ObservableCollection and BindingList
Edit: Some further information about what I've tried and some additional goals.
I cannot use ObservableCollection<T> instead of List<T> as I need to use Exists in the code. Which the former doesn't have.
I need to update the original list when the new object is added, in addition to updating the drop down box.
To summarize my comments below, I attempted adding this:
var bindingList = (BindingList<Branch>) cmbBranches.DataSource;
bindingList.Add(frmAddBranch.NewBranch);
But that results in the object being added to the ComboBox twice. Somehow by calling bindingList.Add it's "resetting" the data source and doubling up. I cannot find any function that "refreshes" the data display once it's bound. Control.ResetBindings() did not work.
Well, it doesn't work that way. The inner List<T> has no change notification mechanism, so adding directly to inner List<T> will not generate any change notification that would eventually reach the combo box. Most convenient way to do what you want is adding the item through the BindingList<T> instead.
I believe you have to add the items directly to the BindingList (but not to the backing Branches list - the BindingList should take care of this for you).
I have a list of StockMultibuy items, and am building a list of those items. Some of the items in the list I want to have different properties than existing items, and so I tried to create a new instance of the item, changed the property that I wanted, and then added it to the list. I wasn't expecting this to update items that were already in the list. Is this expected behaviour? Is there anything I can do to overcome this?
Thanks
StockMultibuy aNewItem = StaticLists.GetStockMultibuyForBarcode(sBarcode);
// If we've got enough items to qualify, then add a free item
if (Reward.StockItemsTaken.Count % (Reward.FreeWhenQualified + Reward.QualifyingQty)
== Reward.QualifyingQty)
{
aNewItem.PromoPrice = 0;
}
// Save this for later so that we know we've added a regular item
else
{
aNewItem.PromoPrice = Convert.ToDecimal(aNewItem.Sell);
}
Reward.StockItemsTaken.Add(aNewItem);
You're not adding several different items to the list, you're adding the same exact item several times and modifying it each time.
Classes in C# are reference types; that means each variable doesn't hold the data for the object itself, it just hold onto a reference to where that object is. Assigning an object to another variable doesn't create a new object, it just copies the reference to that same object.
The issue is that you should be creating a new object (i.e. using the new keyword) each time you go to add a new item to the list.
From the looks of your code it would seem that StaticLists.GetStockMultibuyForBarcode(sBarcode) isn't returning a new item each time; it's just returning the same item, or at the very least one of the existing items. You should be creating a new item, copying some of the values of that item over (if that's your intention) and then adding that new item to the list.
It is expected, it will update because it is a reference. I would just make a copy of the object or clone it if you can.
I've recently run into a snag in my program where using a list of a user defined class has been corrupting data. Example:
class myClass {
public int x;
}
myList<myClass> = new List<myClass>();
myClass myObject = new myClass();
myList.Add(myObject);
myList.Remove(myObject);
When I try to add something to the list, my data is being corrupted. I believe removing objects is also causing problems, although this could be because they're corrupted in the first place. I've seen similar issues here and there and was hoping someone could explain to me what's going on. While the links do provide me with some answers, they're not doing a good job of explaining what's going on behind the scenes.
EDIT: I should have been more clear before about how my data is being corrupted. The values in the fields of my objects are being changed when they are added to the list. For the purposes of my program, the values should all be 0 or 1 but when added to the list change to -1 to 3.
EDIT 2: The source of the problem was an unfamiliarity with C#. Coming from a C++ background I assumed that objects were passed by value and not by reference. Changing my custom class from a class to a struct resolved the issue instantly. Thanks to #MattW !
There isn't too much to go on here - so the following is a guess at the problem ...
Are you changing the value of myObject after adding it to the List<>? Remember that since you are adding a Class to the list (and all Classes are Reference types), that if you change the value of myObject after adding it to the list, it will be changed everywhere (including inside of your List<>).
For example:
List<myClass> myList = new List<myClass>();
myClass myObject = new myClass();
myObject.x = 5;
myList.Add(myObject);
Console.WriteLine(myList[0].x); //this will be 5
myObject.x = 7;
Console.WriteLine(myList[0].x); //this will be 7
Even though you didn't touch the List<> itself, you changed the value of the Referenced object, so it is changed everywhere it is being Referenced.
Check Out Value vs Reference types
I have a class that uses INotifyPropertyChanged and have a List bound to a grid. Every time that object changes it updates the values in the grid. However for historical reasons I want to add that object to a collection/list and bind to a history grid to show all the changes. However every list I have tried seems to subscribe to INotifyPropertyChanged so I only ever get the same amount of items in my history grid.
Is there a list/collection that doesn't subscribe to INotifyPropertyChanged?
Thanks
If your List is a list of references to a class, you will need to do a "deep clone" of the entire list in order to maintain a historical copy. Otherwise, a copy of the list's contents will still be references to the "live" objects which are getting changed. Doing this will require code such as:
// This requires a way to "Clone" your object...
List<YourClass> listCopy = originalList.Select(item => item.Clone()).ToList();
If the list contains value types (struct), you can just create a new List<YourType> and copy the original elements over.
// If your type is a value type, you can just copy the list directly...
var listCopy = originalList.ToList();
Also - this has nothing to do with INotifyPropertyChanged. Lists, themselves, do nothing with the PropertyChanged event.
Consider using Mode=OneTime in your binding. http://msdn.microsoft.com/en-us/magazine/cc163299.aspx#S3