I have a class, which stores an enum
to track if my object is modified, newly created, or no change. If the object is modified, I have additional booleans for each field that is modified.
For instance,
public enum status
{
NoChange,
Created,
Modified
}
private bool? name;
private bool? address;
...
private bool? numberOfDonkeysPurchased;
So the idea is that if the status is modified, then the nullable bools will be either true or false, depending on if these fields are changed.
If the status is not modified, then the nullable bools will be null.
The problem with this is that I have a couple of these fields that I want to check (say 10). Is it valid to create a nullable bool for each field that I am checking, knowing that I maybe be storing a lot of nulls? Or is that not a concern?
Is there a better way to store this?
Thanks!
3 Answers 3
Alternatively, you could use a bitmask via a Flags enum:
[Flags]
public enum Changes
{
None = 0x000,
Name = 0x001,
Address = 0x002,
...
NumberOfDonkeysPurchased = 0x100,
...
}
Then, you merely update the appropriate flags as you call setters:
public string Name
{
get { return name; }
set
{
name = value;
changes |= Changes.Name;
}
}
Checking for changes is just a matter of comparing changes
with Change.None
.
Note: this will be preferable to a collections object if you are transmitting your data between a client and a server, since the bitmask will be stored as a single int or long.
-
\$\begingroup\$ The downside is maintaining the enum when new properties are added/renamed but a neat idea. \$\endgroup\$Trevor Pilley– Trevor Pilley2013年03月12日 22:01:26 +00:00Commented Mar 12, 2013 at 22:01
-
\$\begingroup\$ @TrevorPilley - That's no worse than adding a new
bool
when new properties are added. \$\endgroup\$Bobson– Bobson2013年03月13日 18:16:35 +00:00Commented Mar 13, 2013 at 18:16 -
\$\begingroup\$ @Bobson that's true, it is still an additional maintenance task (especially if it's across multiple objects) unless you have some clever T4 skills and it's practical to use templates \$\endgroup\$Trevor Pilley– Trevor Pilley2013年03月13日 22:13:53 +00:00Commented Mar 13, 2013 at 22:13
-
\$\begingroup\$ Care should be taken when passing this from one platform to another as the bit order may differ. \$\endgroup\$bloudraak– bloudraak2013年03月15日 18:30:28 +00:00Commented Mar 15, 2013 at 18:30
I would suggest that you use a Dictionary, with the property as the key and the bool as the value. e.g.
Dictionary<string, bool?> myProperties = new Dictionary<string, bool?>();
Use reflection to get all the properties from your object. Then you dont have any work when properties gets added/removed.
You can initialise your dictionary with reflection like this:
foreach (PropertyInfo propertyInfo in myObject.GetType().GetProperties()) {
myProperties.Add(propertyInfo.Name, false);
}
Or you can even only add those that are not null.
-
\$\begingroup\$ +1 - This is what I had in mind, but I've been too busy today to write up. Only thing I'd add would be an
PropertyChanged(string propname)
function to called from each property's setter to automatically update the dictionary. \$\endgroup\$Bobson– Bobson2013年03月11日 19:12:11 +00:00Commented Mar 11, 2013 at 19:12 -
\$\begingroup\$ The only downside to this is that if you create 100 instances of that object, you are running that reflection code 100 times. \$\endgroup\$Trevor Pilley– Trevor Pilley2013年03月12日 12:07:41 +00:00Commented Mar 12, 2013 at 12:07
-
\$\begingroup\$ @TrevorPilley: if running the reflection code with each new instance is a concern, it can be optimized by adding a static Set which gets initialized once with the property names. Then each instance only needs to load the map with the field names in the set (and no need to do further reflection on the properties). \$\endgroup\$Sam Goldberg– Sam Goldberg2013年03月13日 13:20:03 +00:00Commented Mar 13, 2013 at 13:20
You could use INotifyPropertyChanged
which would also mean that your object can be databound easily. Something like this may be suitable:
public abstract class ChangeTrackingObject : INotifyPropertyChanged
{
private readonly HashSet<string> changedProperties = new HashSet<string>();
protected ChangeTrackingObject()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public IEnumerable<string> ChangedProperties
{
get
{
return this.changedProperties;
}
}
public bool PropertyWasChanged(string member)
{
return this.changedProperties.Contains(member);
}
protected virtual void OnPropertyChanged([CallerMemberName] string member = "")
{
this.changedProperties.Add(member);
var propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(member));
}
}
}
The [CallerMemberName]
is new in C#5 which does some compile time magic to put the correct string in place.
Using a HashSet<string>
instead of List<string>
ensures that we get unique values only and no duplicates without having to call List<T>.Contains()
public class Thing : ChangeTrackingObject
{
private string address;
private string name;
private int numberOfDonkeysPurchased;
public string Address
{
get
{
return address;
}
set
{
address = value;
this.OnPropertyChanged();
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
this.OnPropertyChanged();
}
}
public int NumberOfDonkeysPurchased
{
get
{
return numberOfDonkeysPurchased;
}
set
{
numberOfDonkeysPurchased = value;
this.OnPropertyChanged();
}
}
}
Example usage:
var thing = new Thing();
System.Console.WriteLine("NumberOfDonkeysPurchased Changed? " + thing.PropertyWasChanged("NumberOfDonkeysPurchased"));
thing.NumberOfDonkeysPurchased = 5;
System.Console.WriteLine("NumberOfDonkeysPurchased Changed? " + thing.PropertyWasChanged("NumberOfDonkeysPurchased"));
System.Console.WriteLine("Changed Properties");
thing.ChangedProperties.ToList().ForEach(System.Console.WriteLine);
-
\$\begingroup\$ I'd move the responsibility for change tracking to some other object that subscribes to
NotifyProperyChanged
. \$\endgroup\$CodesInChaos– CodesInChaos2013年03月13日 08:43:18 +00:00Commented Mar 13, 2013 at 8:43 -
\$\begingroup\$ That is something that would be easy to do if you wanted. \$\endgroup\$Trevor Pilley– Trevor Pilley2013年03月13日 08:46:46 +00:00Commented Mar 13, 2013 at 8:46
bool?
each time you add a new property? Or are you using something to auto-generate your code? In the former case, this looks like very painful code to modify... \$\endgroup\$bool?
will need to be added for each new property. \$\endgroup\$NoChange
mean? Has not changed since the last time I looked at it? This is subjective to a consumer. What if you have multiple consumers? Why not just sore the created time and modified time that is associated with every value, and keep track of it that way. You can use a dict for that. Alternatively, borrow some ideas from git or Clojure - make your objects immutable and track every change by storing a new value in a LinkedList along with who made a change and when. That way you can have all of the diffs. There is more than one way to implement this; it depends on usage. \$\endgroup\$