16

If my understanding of the internal workings of this line is correct:

public int MyInt { get; set; }

Then it behind the scenes does this:

private int _MyInt { get; set; }
Public int MyInt {
    get{return _MyInt;}
    set{_MyInt = value;}
}

What I really need is:

private bool IsDirty { get; set; }

private int _MyInt { get; set; }
Public int MyInt {
    get{return _MyInt;}
    set{_MyInt = value; IsDirty = true;}
}

But I would like to write it something like:

private bool IsDirty { get; set; }

public int MyInt { get; set{this = value; IsDirty = true;} }

Which does not work. The thing is some of the objects I need to do the IsDirty on have dozens of properties and I'm hoping there is a way to use the auto getter/setter but still set IsDirty when the field is modified.

Is this possible or do I just have to resign myself to tripling the amount of code in my classes?

William
  • 1,457
  • 5
  • 21
  • 46
  • Go with IronPython instead. It's on the CLR, but supports aspect-oriented programming, which is what you need. :) – bzlm Feb 15 '11 at 21:37
  • If you're using Visual Studio you may be able to create a code snippet to help save you time at least. – ebrown Feb 15 '11 at 21:42

5 Answers5

25

You'll need to handle this yourself:

private bool IsDirty { get; set; }

private int _myInt; // Doesn't need to be a property
Public int MyInt {
    get{return _myInt;}
    set{_myInt = value; IsDirty = true;}
}

There is no syntax available which adds custom logic to a setter while still using the automatic property mechanism. You'll need to write this with your own backing field.

This is a common issue - for example, when implementing INotifyPropertyChanged.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 1
    That's exactly what he's trying to avoid doing :) – asdfjklqwer Feb 15 '11 at 21:42
  • 1
    @Nathan: Yep - but that's really what he's going to have to do - auto-props don't work with custom logic. That being said, my implementation is different than the OP's - I'm using a field instead of an auto-prop as the backing field. – Reed Copsey Feb 15 '11 at 21:43
  • Bleh, fine then. Thanks for the field note. – William Feb 15 '11 at 21:48
  • No problem, I just realized I should probably also add an if(_MyInt != value) check in there anyway so I will just have to do it the verbose way :) – William Feb 15 '11 at 21:58
  • can't you do this without two fields? if so can you update this? – barlop Jan 30 '19 at 17:12
  • @barlop This is only using a single field, and one auto-prop. You could use the newer syntax, but it'd be the same – Reed Copsey Jan 31 '19 at 20:50
  • @ReedCopsey I don't know if have this right, but having played around with it some more, it looks like if doing only a getter, or if doing a getter and default setter, then you only need the getter/setter field, you don't need another field. But if wanting a getter and setter, where the setter isn't the default setter, then it seems you need the other field. 'cos a non-blank setter has e.g. _abc=value So you need that _abc field. Maybe some non-default /non-blank 'gets' require the property explicitly written in too. – barlop Feb 01 '19 at 03:24
11

Create an IsDirty decorator (design pattern) to wrap some your properties requiring the isDirty flag functionality.

public class IsDirtyDecorator<T>
{
    public bool IsDirty { get; private set; }

    private T _myValue;
    public T Value
    {
        get { return _myValue; }
        set { _myValue = value; IsDirty = true; }
    }
}

public class MyClass
{
    private IsDirtyDecorator<int> MyInt = new IsDirtyDecorator<int>();
    private IsDirtyDecorator<string> MyString = new IsDirtyDecorator<string>();

    public MyClass()
    {
        MyInt.Value = 123;
        MyString.Value = "Hello";
        Console.WriteLine(MyInt.Value);
        Console.WriteLine(MyInt.IsDirty);
        Console.WriteLine(MyString.Value);
        Console.WriteLine(MyString.IsDirty);
    }
}
Simon Hughes
  • 3,534
  • 3
  • 24
  • 45
  • Interesting. Question though, my objects have many properties and I really only want one IsDirty. When any of my properties are updated I want whichever one is updated to set IsDirty = true. Does that still work with your code? – William Feb 15 '11 at 21:55
  • 2
    This will only work for one property at a time. Each property has its own IsDirty flag. If you want a single IsDirty flag for all the properties, If you want it to work for every property, you'll have to modify the Decortor to hold a reference to an IsDirty flag. – Simon Hughes Feb 15 '11 at 22:21
2

You can make it simple or complex. It depends on how much work you want to invest. You can use aspect oriented programming to add the aspect via an IL weaver into the IL code with e.g. PostSharp. Or you can create a simple class that does handle the state for your property. It is so simple that the former approach only pays off if you have really many properties to handle this way.

using System;

class Dirty<T>
{
    T _Value;
    bool _IsDirty;

    public T Value
    {
        get { return _Value; }
        set
        {
            _IsDirty = true;
            _Value = value;
        }
    }

    public bool IsDirty
    {
        get { return _IsDirty; }
    }

    public Dirty(T initValue)
    {
        _Value = initValue;
    }
}

class Program
{
    static Dirty<int> _Integer;
    static int Integer
    {
        get { return _Integer.Value; }
        set { _Integer.Value = value;  }
    }

    static void Main(string[] args)
    {
        _Integer = new Dirty<int>(10);
        Console.WriteLine("Dirty: {0}, value: {1}", _Integer.IsDirty, Integer);
        Integer = 15;
        Console.WriteLine("Dirty: {0}, value: {1}", _Integer.IsDirty, Integer);
    }
}

Another possibility is to use a proxy class which is generated at runtime which does add the aspect for you. With .NET 4 there is a class that does handle this aspect already for you. It is called ExpandObject which does notify you via an event when a property changes. The nice things is that ExpandoObject allows you to define at runtime any amount of properties and you get notifications about every change of a property. Databinding with WPF is very easy with this type.

dynamic _DynInteger = new ExpandoObject();

_DynInteger.Integer = 10;
((INotifyPropertyChanged)_DynInteger).PropertyChanged += (o, e) =>
{
    Console.WriteLine("Property {0} changed", e.PropertyName);
};

Console.WriteLine("value: {0}", _DynInteger.Integer );
_DynInteger.Integer = 20;
 Console.WriteLine("value: {0}", _DynInteger.Integer);

Yours, Alois Kraus

Alois Kraus
  • 13,229
  • 1
  • 38
  • 64
2

I'm going to add on to Simon Hughes' answer. I propose the same thing, but add a way to allow the decorator class to update a global IsDirty flag automatically. You may find it to be less complex to do it the old-fashioned way, but it depends on how many properties you're exposing and how many classes will require the same functionality.

public class IsDirtyDecorator<T>
{
    private T _myValue;
    private Action<bool> _changedAction;

    public IsDirtyDecorator<T>(Action<bool> changedAction = null)
    {
        _changedAction = changedAction;
    }

    public bool IsDirty { get; private set; }

    public T Value
    {
        get { return _myValue; }
        set
        {
            _myValue = value;
            IsDirty = true;
            if(_changedAction != null)
                _changedAction(IsDirty);
        }
    }
}

Now you can have your decorator class automatically update some other IsDirty property in another class:

class MyObject
{
    private IsDirtyDecorator<int> _myInt = new IsDirtyDecorator<int>(onValueChanged);
    private IsDirtyDecorator<int> _myOtherInt = new IsDirtyDecorator<int>(onValueChanged);

    public bool IsDirty { get; private set; }

    public int MyInt
    {
        get { return _myInt.Value; }
        set { _myInt.Value = value; }
    }

    public int MyOtherInt
    {
        get { return _myOtherInt.Value; }
        set { _myOtherInt.Value = value; }
    }

    private void onValueChanged(bool dirty)
    {
        IsDirty = true;
    }

}
Phil
  • 6,561
  • 4
  • 44
  • 69
1

I have created a custom Property<T> class to do common operations like that. I haven't used it thoroughly yet though, but it could be used in this scenario.

Code can be found here: http://pastebin.com/RWTWNNCU

You could use it as follows:

readonly Property<int> _myInt = new Property<int>();
public int MyInt
{
    get { return _myInt.GetValue(); }
    set { _myInt.SetValue( value, SetterCallbackOption.OnNewValue, SetDirty ); }
}

private void SetDirty( int oldValue, int newValue )
{
    IsDirty = true;
}

The Property class handles only calling the passed delegate when a new value is passed thanks to the SetterCallbackOption parameter. This is default so it can be dropped.

UPDATE:

This won't work apparently when you need to support multiple types (besides int), because the delegate won't match then. You could ofcourse always adjust the code to suit your needs.

Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161