I have a List<AbilityEffect> effects and a lot of sub-classes of AbilityEffect, such as DamageEffect, HealEffect e.t.c. that HAVE [System.Serializable] property on it.
If I create class with field such as DamageEffect - Default editor will draw it perfectly! (And other effects too!)
I've added a ContextMenu Attribute to this function in AbilityData.cs
[ContextMenu(Add/DamageEffect)]
public static void AddDamageEffect()
{
effects.Add(new DamageEffect());
}
BUT default Unity Editor draws it if was an AbilityEffect, NOT a DamageEffect!
I've write some Custom Editor for class, that contains List<AbilityEffect> effects = new List<AbilitiEffect>(), write code that draws a custom list! But how do I tell a Editor to draw a DamageEffect specifically, NOT AbilityEffect?
I'll put some code below:
Ability Data Class
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "New Ability", menuName = "ScriptableObject/Ability")]
public class AbilityData : ScriptableObject
{
public int cooldown = 0;
public int range = 1;
public List<AbilityEffect> effects = new List<AbilityEffect>();
public bool showEffects = false;
[ContextMenu("Add/DamageEffect")]
public void AddDamageEffect()
{
effects.Add(new DamageEffect());
}
}
Ability Data Editor Class
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
[CustomEditor(typeof(AbilityData))]
public class AbilityEditor : Editor
{
public override void OnInspectorGUI()
{
var ability = (AbilityData)target;
DrawDetails(ability);
DrawEffects(ability);
}
private static void DrawEffects(AbilityData ability)
{
EditorGUILayout.Space();
ability.showEffects = EditorGUILayout.Foldout(ability.showEffects, "Effects", true);
if (ability.showEffects)
{
EditorGUI.indentLevel++;
List<AbilityEffect> effects = ability.effects;
int size = Mathf.Max(0, EditorGUILayout.IntField("Size", effects.Count));
while (size > effects.Count)
{
effects.Add(null);
}
while (size < effects.Count)
{
effects.RemoveAt(effects.Count - 1);
}
for (int i = 0; i < effects.Count; i++)
{
DrawEffect(effects[i], i);
}
EditorGUI.indentLevel--;
}
}
private static void DrawDetails(AbilityData ability)
{
EditorGUILayout.LabelField("Details");
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Cooldown", GUILayout.MaxWidth(60));
ability.cooldown = EditorGUILayout.IntField(ability.cooldown);
EditorGUILayout.LabelField("Range", GUILayout.MaxWidth(40));
ability.range = EditorGUILayout.IntField(ability.range);
EditorGUILayout.EndHorizontal();
}
private static void DrawEffect(AbilityEffect effect, int index)
{
//if (effect is DamageEffect)
// effect = EditorGUILayout
// HOW??
}
}
Ability Effect class (NOT ABSTRACT)
[System.Serializable]
public class AbilityEffect
{
public virtual void Affect() { }
}
Damage Effect Class
[System.Serializable]
public class DamageEffect : AbilityEffect
{
public int damageAmout = 1;
public override void Affect() { ... }
}
Because of how Serialization works, once you deserialize some data Unity will try to populate an object instance based on the type specified in the class definition. If you have a List<AbilityEffect> Unity won't be able to differentiate which specific AbilityEffect you previously serialized. There is really one solution, change AbilityEffect to be a ScriptableObject, so that Unity doesn't actually serialize them as raw data but as GUID references, so that the referenced assets know by themselves what subtype of AbilityEffect they are. The downside is that this way all your effects will have to be assets in your Assets folder.
First of all: Note that since Unity 2021 the foldout is built-in default for all lists and arrays so actually I see absolutely no need for a custom editor at all really (at least for the list part) ;)
There is a couple of problems with your approach.
BUT default Unity Editor draws it if was an AbilityEffect, NOT a DamageEffect.
Yes, because it is serialized only as a AbilityEffect! For the Serializer all items in that list are of type AbilityEffect and it won't go any further.
So even if you can manage to add subclass items it will only be temporary! After e.g. saving, closing Unity and reopening all the subtypes should be converted to AbilityEffect because that's tue only type the Serializer actually sees for those.
My recommendation would be to rather make your AbilityEffect also of type ScriptableObject. This way you don't even have to bother with a custom drawer for them at all and could have as many instances with different types and configurations as you want, reuse them etc.
This said now a general thing: Don't directly go through the target in editors! (except you know exactly what you are doing)
This doesn't mark this object as "dirty", doesn't work with Undo/Redo and worst of all - it won't save these changes persistently!
Always rather go through the serializedObject and the SerializedPropertys.
[CustomEditor(typeof(AbilityData))]
public class AbilityEditor : Editor
{
SerializedProperty cooldown;
SerializedProperty range;
SerializedProperty effects;
SerializedProperty showEffects;
private void OnEnable ()
{
// Link up the serialized fields you will access
cooldown = serializedObject.FindProperty(nameof(AbilityData.cooldown));
range = serializedObject.FindProperty(nameof(AbilityData.range));
effects = serializedObject.FindProperty(nameof(AbilityData.effects));
showEffects = serializedObject.FindProperty(nameof(AbilityData.showEffects));
}
public override void OnInspectorGUI()
{
// refresh current actual values into the editor
serializedObject.Update();
DrawDetails();
DrawEffects();
// write back any changed values from the editor back to the actual object
// This handles all marking dirty, saving and handles Undo/Redo
serializedObject.ApplyModifiedProperties();
}
private void DrawEffects()
{
// Now always ever only read and set values via the SerializedPropertys
EditorGUILayout.Space();
showEffects.boolValue = EditorGUILayout.Foldout(showEffects.boolValue, effects.displayName, true);
if (showEffects.boolValue)
{
EditorGUI.indentLevel++;
// This already handles all the list drawing by default
EditorGUILayout.PropertyField(effects, GUIContent.none, true);
EditorGUI.indentLevel--;
}
}
private void DrawDetails()
{
EditorGUILayout.LabelField("Details");
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(cooldown.displayName, GUILayout.MaxWidth(60));
cooldown.intValue = EditorGUILayout.IntField(cooldown.intValue);
EditorGUILayout.LabelField(range.displayName, GUILayout.MaxWidth(40));
range.intValue = EditorGUILayout.IntField(range.intValue);
EditorGUILayout.EndHorizontal();
}
}
Now if you really really want to customize the behavior of the list drawing you could use a ReorderableList and can then implement a drawer for each element and there you could indeed perform a type check.
But as said I wouldn't go this way at all since the Serializer doesn't support it anyway.
I am using a component system for certain Objects(just like components in Unity). Each component inheriths from the class Component. The objects then have a list of components.
What I am trying to implement is a GetComponent() method. It returns the component of type T if it exists, otherwise it returns null. Let's say that an Object has the Renderer Component. I want to be able to call the Draw method in the Renderer class:
Object.GetComponent<Renderer>().Draw();
The problem is that when I call this function I get the parent class(of type: Component) instead of the child class. Because of that, when I try the code above ^^ I get the error; 'Component' does not contain a definition for 'Draw' (...)
Code:
internal abstract class GameObject : Object
{
//Props
public string name;
public GameObject? parent;
public Transform transform
{
get
{
return (parent == null) ? transform : parent.transform;
}
private set
{
transform = value;
}
}
public bool isActive = true;
private List<Component> components = new List<Component>();
//Constructor
public GameObject(string name, GameObject? parent = null)
{
this.name = name;
transform = new Transform(this);
components.Add(transform);
this.parent = parent;
}
public void AddComponent(Component component)
{
components.Add(component);
}
//Method that is not working properly
public Component GetComponent<T>()
{
foreach (Component component in components)
{
if (typeof(T) == component.GetType())
return component;
}
return null;
}
}
}
Instead of returning Component, return T. Also, use is for type checking and casting:
public T GetComponent<T>() where T : Component
{
foreach(var component in components)
{
if(component is T value) return value;
}
return default;
}
BTW, what if you have two components of the same type? You might want to consider using Linq's OfType<T>() here instead of a foreach.
The return value of the method needs to be T
public T GetComponent<T>()
{
// your code
}
GetComponent<Renderer>().Draw() is 2 statements
GetComponent<Renderer>()
Draw()
By writing it like so:
// 'component' is of type 'Component' here, not 'Renderer'
var component = GetComponent<Renderer>();
component.Draw();
It's obvious why it doesn't work with your current code and why it does with the updated one.
P.S I'd personally also add a constraint to make sure we can only GetComponent<T> with types that actually are components, like so:
public T GetComponent<T>() where T : Component
{
}
With the where we're forcing compile time checks that this method can only be called with types that inherit from Component
I have two similar classes ClassA and ClassB. Both classes contain a bool:
In Class A:
[SerializeField]
private bool _overwrite = true;
public bool overwrite
{
get { return _overwrite; }
set { _overwrite = value; }
}
In Class B:
[SerializeField]
private bool _hide = true;
public bool hide
{
get { return _hide; }
set { _hide = value; }
}
Both scripts have a CustomEditor script. In both Editor scripts, inside the OnInspectorGUI() method the following two lines are used to add the respective bool's to the Inspector.
ClassA.overwrite = EditorGUILayout.ToggleLeft("Overwrite", ClassA.overwrite);
ClassB.hide = EditorGUILayout.ToggleLeft("Hide", ClassB.hide);
When I add ClassA to a GameObject, the "Overwrite" field is unchecked, however when I add ClassB to a GameObject, the "Hide" field is checked.
I don't understand what is different, or what other factor is involved in setting a default / initial value for a property.
Ideally I want them both to be checked by default.
Any ideas what I might be missing?
Thanks for your time,
Liam
The Reset method of MonoBehaviours seems that it will provide the functionality you are looking for. It will look something like this:
void Reset()
{
_overwrite = true;
}
What is grabObject?
If it is the component variable which you have found a reference to in the OnEnable method using the target variable of the Editor script, then simply changing your editor scripts to:
grabObject.overwrite = EditorGUILayout.ToggleLeft("Overwrite", grabObject.overwrite);
and:
grabObject.hide = EditorGUILayout.ToggleLeft("Hide", grabObject.hide);
should solve your problem.
Consider the following example:
public class Foo
{
private string _text;
[BsonElement("text"), BsonRequired]
public string Text
{
get { return _text; }
set
{
_text = value;
Bar(_text);
}
}
private void Bar(string text)
{
//Only relevant when Text is set by the user of the class,
//not during deserialization
}
}
The setter of the Text property and, subsequently, the method Bar are called both when the user of the class assigns a new value to the property and during object deserialization by the MongoDB C# driver. What I need is to ensure that Bar is called only when the Text property is set by the user and not during deserialization.
I see two solutions which don't really suit me:
The first is to move the BsonElement attribute to the backing field. However, as far as I know, the BsonElement attribute is used in query building by the MongoDB C# driver, so I will lose the ability to use the Text property in queries.
The second solution is to make the Text setter private and add a method through which the user of the class will set the Text property, and in which the Bar method would be called. However, the Text setter is used very often in the existing solution, and I'm a bit reluctant to change 70+ calls across all files. Plus, the code will become less readable.
Is there any cleaner way to separate deserialization and user-prompted property change while retaining the BsonElement attribute on the property?
I know this question is old, but I'd still like to help for other people stumbling on this issue as I have done.
It basically boils down to something very simple: serialization and deserialization are not limited to public fields and properties!
The next example will cover the original question without having to invent dubious secondary properties:
public class Foo
{
[BsonElement("Text"), BsonRequired]
private string _text;
[BsonIgnore]
public string Text
{
get { return _text; }
set
{
_text = value;
Bar(_text);
}
}
private void Bar(string text)
{
//Only relevant when Text is set by the user of the class,
//not during deserialization
}
}
Simply put your BsonElement class on the backing field and tell it to BsonIgnore the property.
You can do whatever you like in the getter and setter without having to worry about deserialization which now occurs on private field level.
Hope this helps somebody!
Why not create a seperate property for the users and for the DB for the same private variable, something like this,
public class Foo
{
private string _text;
[BsonElement("text"), BsonRequired]
public string TextDB
{
get { return _text; }
set
{
_text = value;
}
}
[BsonIgnore]
public string Text
{
get { return _text; }
set
{
_text = value;
Bar(_text);
}
}
private void Bar(string text)
{
//Only relevant when Text is set by the user of the class,
//not during deserialization
}
}
You can use a little trick an implement a kind of property listener.
The usage would be:
// Working with some foo here...
var foo = new Foo();
foo.Text = "Won't fire anything";
using (var propertyListener = new FooPropertiesListener(foo))
{
foo.Text = "Something will fire you listener";
}
// Some more work with foo here...
foo.Text = "Won't fire anything";
And the implementation behind it, something like:
FooPropertiesListener
public class FooPropertiesListener : IDisposable
{
private readonly Foo Foo;
public FooPropertiesListener(Foo foo)
{
this.Foo = foo;
this.Foo.PropertiesListener = this;
}
public void Bar(string text)
{
//Only relevant when Text is set by the user of the class,
//not during deserialization
}
public void Dispose()
{
Foo.PropertiesListener = null;
}
}
Foo
public class Foo
{
internal FooPropertiesListener PropertiesListener;
private string _text;
[BsonElement("text"), BsonRequired]
public string Text
{
get { return _text; }
set
{
_text = value;
if (PropertiesListener != null)
{
PropertiesListener.Bar(_text);
}
}
}
}
The following is a simple example of an enum which defines the state of an object and a class which shows the implementation of this enum.
public enum StatusEnum
{
Clean = 0,
Dirty = 1,
New = 2,
Deleted = 3,
Purged = 4
}
public class Example_Class
{
private StatusEnum _Status = StatusEnum.New;
private long _ID;
private string _Name;
public StatusEnum Status
{
get { return _Status; }
set { _Status = value; }
}
public long ID
{
get { return _ID; }
set { _ID = value; }
}
public string Name
{
get { return _Name; }
set { _Name = value; }
}
}
when populating the class object with data from the database, we set the enum value to "clean". with the goal of keeping most of the logic out of the presentation layer, how can we set the enum value to "dirty" when a property is changed.
i was thinking something along the lines of;
public string Name
{
get { return _Name; }
set
{
if (value != _Name)
{
_Name = value;
_Status = StatusEnum.Dirty;
}
}
}
in the setter of each property of the class.
does this sound like a good idea, does anyone have any better ideas on how the dirty flag can be assigned without doing so in the presentation layer.
When you really do want a dirty flag at the class level (or, for that matter, notifications) - you can use tricks like below to minimise the clutter in your properties (here showing both IsDirty and PropertyChanged, just for fun).
Obviously it is a trivial matter to use the enum approach (the only reason I didn't was to keep the example simple):
class SomeType : INotifyPropertyChanged {
private int foo;
public int Foo {
get { return foo; }
set { SetField(ref foo, value, "Foo"); }
}
private string bar;
public string Bar {
get { return bar; }
set { SetField(ref bar, value, "Bar"); }
}
public bool IsDirty { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void SetField<T>(ref T field, T value, string propertyName) {
if (!EqualityComparer<T>.Default.Equals(field, value)) {
field = value;
IsDirty = true;
OnPropertyChanged(propertyName);
}
}
protected virtual void OnPropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You might also choose to push some of that into an abstract base class, but that is a separate discussion
One option is to change it on write; another is to keep a copy of all the original values and compute the dirtiness when anyone asks for it. That has the added benefit that you can tell exactly which fields have changed (and in what way) which means you can issue minimal update statements and make merge conflict resolution slightly easier.
You also get to put all the dirtiness-checking in one place, so it doesn't pollute the rest of your code.
I'm not saying it's perfect, but it's an option worth considering.
If you want to implement it in this way, and you want to reduce the amount of code, you might consider applying Aspect Oriented Programming.
You can for instance use a compile-time weaver like PostSharp , and create an 'aspect' that can be applied to properties. This aspect then makes sure that your dirty flag is set when appropriate.
The aspect can look like this:
[Serializable]
[AttributeUsage(AttributeTargets.Property)]
public class ChangeTrackingAttribute : OnMethodInvocationAspect
{
public override void OnInvocation( MethodInvocationEventArgs e )
{
if( e.Delegate.Method.ReturnParameter.ParameterType == typeof(void) )
{
// we're in the setter
IChangeTrackable target = e.Delegate.Target as IChangeTrackable;
// Implement some logic to retrieve the current value of
// the property
if( currentValue != e.GetArgumentArray()[0] )
{
target.Status = Status.Dirty;
}
base.OnInvocation (e);
}
}
}
Offcourse, this means that the classes for which you want to implement ChangeTracking, should implement the IChangeTrackable interface (custom interface), which has at least the 'Status' property.
You can also create a custom attribute ChangeTrackingProperty, and make sure that the aspect that has been created above, is only applied to properties that are decorated with this ChangeTrackingProperty attribute.
For instance:
public class Customer : IChangeTrackable
{
public DirtyState Status
{
get; set;
}
[ChangeTrackingProperty]
public string Name
{ get; set; }
}
This is a little bit how I see it.
You can even make sure that PostSharp checks at compile-time whether classes that have properties that are decorated with the ChangeTrackingProperty attribute, implement the IChangeTrackable interface.
This method is based on a set of different concepts provided in this thread. I thought i'd put it out there for anyone that is looking for a way to do this cleanly and efficiently, as i was myself.
The key of this hybrid concept is that:
You don't want to duplicate the data to avoid bloating and resource hogging;
You want to know when the object's properties have changed from a given original/clean state;
You want to have the IsDirty flag be both accurate, and require little processing time/power to return the value; and
You want to be able to tell the object when to consider itself clean again. This is especially useful when building/working within the UI.
Given those requirements, this is what i came up with, and it seems to be working perfectly for me, and has become very useful when working against UIs and capturing user changes accurately. I have also posted an "How to use" below to show you how I use this in the UI.
The Object
public class MySmartObject
{
public string Name { get; set; }
public int Number { get; set; }
private int clean_hashcode { get; set; }
public bool IsDirty { get { return !(this.clean_hashcode == this.GetHashCode()); } }
public MySmartObject()
{
this.Name = "";
this.Number = -1;
MakeMeClean();
}
public MySmartObject(string name, int number)
{
this.Name = name;
this.Number = number;
MakeMeClean();
}
public void MakeMeClean()
{
this.clean_hashcode = this.Name.GetHashCode() ^ this.Number.GetHashCode();
}
public override int GetHashCode()
{
return this.Name.GetHashCode() ^ this.Number.GetHashCode();
}
}
It's simple enough and addresses all of our requirements:
The data is NOT duplicated for the dirty check...
This takes into account all property changes scenarios (see scenarios below)...
When you call the IsDirty property, a very simple and small Equals operation is performed and it is fully customizable via the GetHashCode override...
By calling the MakeMeClean method, you now have a clean object again!
Of course you can adapt this to encompass a bunch of different states... it's really up to you. This example only shows how to have a proper IsDirty flag operation.
Scenarios
Let's go over some scenarios for this and see what comes back:
Scenario 1
New object is created using empty constructor,
Property Name changes from "" to "James",
call to IsDirty returns True! Accurate.
Scenario 2
New object is created using paramters of "John" and 12345,
Property Name changes from "John" to "James",
Property Name changes back from "James" to "John",
Call to IsDirty returns False. Accurate, and we didn't have to duplicate the data to do it either!
How to use, a WinForms UI example
This is only an example, you can use this in many different ways from a UI.
Let's say you have a two forms ([A] and [B]).
The first([A]) is your main form, and the second([B]) is a form that allows the user to change the values within the MySmartObject.
Both the [A] and the [B] form have the following property declared:
public MySmartObject UserKey { get; set; }
When the user clicks a button on the [A] form, an instance of the [B] form is created, its property is set and it is displayed as a dialog.
After form [B] returns, the [A] form updates its property based on the [B] form's IsDirty check. Like this:
private void btn_Expand_Click(object sender, EventArgs e)
{
SmartForm form = new SmartForm();
form.UserKey = this.UserKey;
if(form.ShowDialog() == DialogResult.OK && form.UserKey.IsDirty)
{
this.UserKey = form.UserKey;
//now that we have saved the "new" version, mark it as clean!
this.UserKey.MakeMeClean();
}
}
Also, in [B], when it is closing, you can check and prompt the user if they are closing the form with unsaved changes in it, like so:
private void BForm_FormClosing(object sender, FormClosingEventArgs e)
{
//If the user is closing the form via another means than the OK button, or the Cancel button (e.g.: Top-Right-X, Alt+F4, etc).
if (this.DialogResult != DialogResult.OK && this.DialogResult != DialogResult.Ignore)
{
//check if dirty first...
if (this.UserKey.IsDirty)
{
if (MessageBox.Show("You have unsaved changes. Close and lose changes?", "Unsaved Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
e.Cancel = true;
}
}
}
As you can see from the examples above, this can be a very useful thing to have since it really streamlines the UI.
Caveats
Every time you implement this, you have to customize it to the object you're using. E.g.: there's no "easy" generic way of doing this without using reflection... and if you use reflection, you lose efficiency, especially in large and complex objects.
Hopefully this helps someone.
Take a look at PostSharp (http://www.postsharp.org/).
You can easily create a Attribute which marks it as dirty you can add the attrubute to each property that needs it and it keeps all your code in one place.
Roughly speaking Create an interface which has your status in make the class implement it.
Create an attribute which can be applied on properties and cast to your interface in order to set the value when something changes one of the marked properties.
Your approach is basically how I would do it. I would just
remove the setter for the Status property:
public StatusEnum Status
{
get { return _Status; }
// set { _Status = value; }
}
and instead add a function
public SetStatusClean()
{
_Status = StatusEnum.Clean;
}
As well as SetStatusDeleted() and SetStatusPurged(), because I find it better indicates the intention.
Edit
Having read the answer by Jon Skeet, I need to reconsider my approach ;-) For simple objects I would stick with my way, but if it gets more complex, his proposal would lead to much better organised code.
If your Example_Class is lightweight, consider storing the original state and then comparing the current state to the original in order to determine the changes. If not your approach is the best because stroing the original state consumes a lot of system resources in this case.
Apart from the advice of 'consider making your type immutable', here's something I wrote up (and got Jon and Marc to teach me something along the way)
public class Example_Class
{ // snip
// all properties are public get and private set
private Dictionary<string, Delegate> m_PropertySetterMap;
public Example_Class()
{
m_PropertySetterMap = new Dictionary<string, Delegate>();
InitializeSettableProperties();
}
public Example_Class(long id, string name):this()
{ this.ID = id; this.Name = name; }
private void InitializeSettableProperties()
{
AddToPropertyMap<long>("ID", value => { this.ID = value; });
AddToPropertyMap<string>("Name", value => { this.Name = value; });
}
// jump thru a hoop because it won't let me cast an anonymous method to an Action<T>/Delegate
private void AddToPropertyMap<T>(string sPropertyName, Action<T> setterAction)
{ m_PropertySetterMap.Add(sPropertyName, setterAction); }
public void SetProperty<T>(string propertyName, T value)
{
(m_PropertySetterMap[propertyName] as Action<T>).Invoke(value);
this.Status = StatusEnum.Dirty;
}
}
You get the idea.. possible improvements: Use constants for PropertyNames & check if property has really changed.
One drawback here is that
obj.SetProperty("ID", 700); // will blow up int instead of long
obj.SetProperty<long>("ID", 700); // be explicit or use 700L
Here is how i do it.
In cases where i do not need to test for specific fields being dirty,
I have an abstract class:
public abstract class SmartWrap : ISmartWrap
{
private int orig_hashcode { get; set; }
private bool _isInterimDirty;
public bool IsDirty
{
get { return !(this.orig_hashcode == this.GetClassHashCode()); }
set
{
if (value)
this.orig_hashcode = this.orig_hashcode ^ 108.GetHashCode();
else
MakeClean();
}
}
public void MakeClean()
{
this.orig_hashcode = GetClassHashCode();
this._isInterimDirty = false;
}
// must be overridden to return combined hashcodes of fields testing for
// example Field1.GetHashCode() ^ Field2.GetHashCode()
protected abstract int GetClassHashCode();
public bool IsInterimDirty
{
get { return _isInterimDirty; }
}
public void SetIterimDirtyState()
{
_isInterimDirty = this.IsDirty;
}
public void MakeCleanIfInterimClean()
{
if (!IsInterimDirty)
MakeClean();
}
/// <summary>
/// Must be overridden with whatever valid tests are needed to make sure required field values are present.
/// </summary>
public abstract bool IsValid { get; }
}
}
As well as an interface
public interface ISmartWrap
{
bool IsDirty { get; set; }
void MakeClean();
bool IsInterimDirty { get; }
void SetIterimDirtyState();
void MakeCleanIfInterimClean();
}
This allows me to do partial saves, and preserve the IsDirty state if there is other details to save. Not perfect, but covers a lot of ground.
Example of usage with interim IsDirty State (Error wrapping and validation removed for clarity):
area.SetIterimDirtyState();
if (!UpdateClaimAndStatus(area))
return false;
area.MakeCleanIfInterimClean();
return true;
This is good for most scenarios, however for some classes i want to test for each field with a backing field of original data, and either return a list of changes or at least an enum of fields changed.
With an enum of fields changed i can then push that up through a message chain for selective update of fields in remote caches.
You could also think about boxing your variables, which comes at a performance cost, but also has its merits. It is pretty consise and you cannot accidentally change a value without setting your dirty status.
public class Variable<T>
{
private T _value;
private readonly Action<T> _onValueChangedCallback;
public Variable(Action<T> onValueChangedCallback, T value = default)
{
_value = value;
_onValueChangedCallback = onValueChangedCallback;
}
public void SetValue(T value)
{
if (!EqualityComparer<T>.Default.Equals(_value, value))
{
_value = value;
_onValueChangedCallback?.Invoke(value);
}
}
public T GetValue()
{
return _value;
}
public static implicit operator T(Variable<T> variable)
{
return variable.GetValue();
}
}
and then hook in a callback that marks your class as dirty.
public class Example_Class
{
private StatusEnum _Status = StatusEnum.New;
private Variable<long> _ID;
private Variable<string> _Name;
public StatusEnum Status
{
get { return _Status; }
set { _Status = value; }
}
public long ID => _ID;
public string Name => _Name;
public Example_Class()
{
_ID = new Variable<long>(l => Status = StatusEnum.Dirty);
_Name = new Variable<string>(s => Status = StatusEnum.Dirty);
}
}
Another method is to override the GetHashCode() method to somthing like this:
public override int GetHashCode() // or call it GetChangeHash or somthing if you dont want to override the GetHashCode function...
{
var sb = new System.Text.StringBuilder();
sb.Append(_dateOfBirth);
sb.Append(_marital);
sb.Append(_gender);
sb.Append(_notes);
sb.Append(_firstName);
sb.Append(_lastName);
return sb.ToString.GetHashCode();
}
Once loaded from the database, get the hash code of the object. Then just before you save check if the current hash code is equal to the previous hash code. if they are the same, don't save.
Edit:
As people have pointed out this causes the hash code to change - as i use Guids to identify my objects, i don't mind if the hashcode changes.
Edit2:
Since people are adverse to changing the hash code, instead of overriding the GetHashCode method, just call the method something else. The point is detecting a change not whether i use guids or hashcodes for object identification.