c# marking class property as dirty

前端 未结 11 2240
盖世英雄少女心
盖世英雄少女心 2020-11-29 19:32

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 Status         


        
相关标签:
11条回答
  • 2020-11-29 20:10

    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.

    0 讨论(0)
  • 2020-11-29 20:11

    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

    0 讨论(0)
  • 2020-11-29 20:13

    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.

    0 讨论(0)
  • 2020-11-29 20:14

    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:

    1. You don't want to duplicate the data to avoid bloating and resource hogging;
    2. You want to know when the object's properties have changed from a given original/clean state;
    3. You want to have the IsDirty flag be both accurate, and require little processing time/power to return the value; and
    4. 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:

    1. The data is NOT duplicated for the dirty check...
    2. This takes into account all property changes scenarios (see scenarios below)...
    3. When you call the IsDirty property, a very simple and small Equals operation is performed and it is fully customizable via the GetHashCode override...
    4. 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.

    0 讨论(0)
  • 2020-11-29 20:17

    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.

    0 讨论(0)
提交回复
热议问题