1

I need binding between two similar objects (C#):

public class TypeA
{
     public int I;
     public string S;
}

public class TypeB
{
     public IntField I;
     public StringField S;
}

When a field in TypeA changes I need to update the matching field in TypeB.

IntField is an object that has a Value field of int type, so that updating TypeB can be written as:

bInstance.I.Value = aInstance.I;

If I understand correctly, if I use INotifyPropertyChanged in order to bind TypeB to TypeA, it'll cause boilerplate:

aInstance.PropertyChanged += (sender, args) =>
{
    if (args.PropertyName == "I")
        this.I.Value = sender.I;
    if (args.PropertyName == "S")
        this.S.Value = sender.S;
};

Also:

  • I have access to the code in both types, and I'd rather not change TypeB.
  • I have ~15 pairs of types like TypeA and TypeB - I'd like to avoid boilerplate.
  • Performance is very important, so reflection is not a preferred option.
  • Perhaps static reflection is an option? I've heard about it, but I'm not sure about:
    • How to use it without boilerplate.
    • Its performance.
    • Using it for different instances of pairs of the same type (i.e. a1Instance->b1Instance, a2Intance->b2Instance, etc.).

Edit:

IntField is a class. It's used for another type of data binding that exists in the system (complex, and the entire system relies on this). It inherits from a class that represents a general bindable field. here's part of it:

public class IntField : GeneralField
{
    private int _value;
    public int Value
    {
        get { return _value; }
        set
        {
            IsDirty = true;
            _value = value;
        }
    }

    // ... a couple of abstract method implementations go here (setting _value, and getting value in a non-type specific way)
}
Tom
  • 964
  • 9
  • 25
  • 2
    Well, you won't be able to do *anything* while they remain as fields - I assume you're ok with making those into properties? Can you also describe (or better: show) `IntField` / `StringField` ? are they `struct` or `class`? (i.e. which?) this could matter. Also; what is it you are actually trying to *do* here? what is the *purpose* of `IntField` / `StringField` ? – Marc Gravell Jun 25 '13 at 07:29
  • Thank you Marc! I'm ok with making TypeA's fields into properties, and I updated the question with some clarifications. It's a bit crazy, but I have client-server data binding with IntField and StringField, and now I need another type of binding for a new server framework I'm supposed to use. – Tom Jun 25 '13 at 08:08

1 Answers1

1

If you don't want lots of manual coding, something reflection-based or meta-programming-based is going to be your best bet. For example:

static void Entwine(INotifyPropertyChanged source, object target)
{
    source.PropertyChanged += (sender,args) =>
    {
        var prop = target.GetType().GetProperty(args.PropertyName);
        if(prop != null)
        {
            var field = prop.GetValue(target) as GeneralField;
            if(field != null)
            {
                var newVal = source.GetType().GetProperty(args.PropertyName)
                                   .GetValue(source);
                field.SetValue(newVal); // <=== some method on GeneralField
            }
        }
    };
}

In many cases this will be fine, but if the reflection is genuinely a problem, tools like FastMember can help:

static void Entwine(INotifyPropertyChanged source, object target)
{
    var sourceAccessor = ObjectAccessor.Create(source);
    var targetAccessor = ObjectAccessor.Create(target);
    source.PropertyChanged += (sender, args) =>
    {
        var field = targetAccessor[args.PropertyName] as GeneralField;
        if (field != null)
        {
            var newVal = sourceAccessor[args.PropertyName];
            field.SetValue(newVal);
        }
    };
}

This is significantly faster than reflection - it uses a lot of tricks to avoid pain. That just leaves the need for something like:

abstract class GeneralField
{
    // ...
    public abstract void SetValue(object value);
}
class Int32Field : GeneralField
{
    // ...
    public override void SetValue(object value)
    {
        Value = (int)value;
    }
}

And of course your INotifyPropertyChanged implementation, for example:

public class TypeA : INotifyPropertyChanged
{
    private int i;
    private string s;
    public int I
    {
        get { return i; }
        set { SetField(ref i, value); }
    }
    public string S
    {
        get { return s; }
        set { SetField(ref s, value); }
    }
    private void SetField<T>(ref T field, T value,
        [CallerMemberName]string propertyName = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            var handler = PropertyChanged;
            if (handler != null) handler(
                this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Awesome! I understand how using closure and a wrapper can solve the issue. BTW, you nailed it with the "SetValue" method, I already have it. :) You rule! I'm curious about the tricks. Is the source here: https://code.google.com/p/fast-member/ ? – Tom Jun 25 '13 at 10:32
  • @Tom yes, that is the source; it uses per-type IL-generation (which is then cached and re-used for all later occurrences of that type); they key point being that it turns string-based member access into tight IL – Marc Gravell Jun 25 '13 at 11:57