C# Связывание объектов данных

Мне нужно связывание между двумя похожими объектами (C#):

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

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

Когда поле в TypeA изменяется, мне нужно обновить соответствующее поле в TypeB.

IntField - это объект, имеющий поле Value типа int, поэтому обновление TypeB можно записать в виде:

bInstance.I.Value = aInstance.I;

Если я правильно понимаю, если я использую INotifyPropertyChanged для привязки TypeB к TypeA, это вызовет шаблон:

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

Также:

  • У меня есть доступ к коду в обоих типах, и я бы не хотел менять TypeB.
  • У меня есть ~15 пар типов, таких как TypeA и TypeB - я бы хотел избежать шаблонов.
  • Производительность очень важна, поэтому рефлексия не является предпочтительным вариантом.
  • Возможно, статическое отражение является вариантом? Я слышал об этом, но я не уверен в:
    • Как использовать это без шаблона.
    • Его производительность.
    • Использование его для разных экземпляров пар одного и того же типа (т.е. a1Instance->b1Instance, a2Intance->b2Instance и т. Д.).

Редактировать:

IntField - это класс. Он используется для другого типа привязки данных, который существует в системе (сложный, и вся система полагается на это). Он наследуется от класса, который представляет общее связываемое поле. вот часть этого:

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)
}

1 ответ

Решение

Если вы не хотите много ручного кодирования, то лучшим выбором будет что-то основанное на рефлексии или метапрограммировании. Например:

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
            }
        }
    };
}

Во многих случаях это будет хорошо, но если отражение действительно является проблемой, могут помочь такие инструменты, как FastMember:

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);
        }
    };
}

Это значительно быстрее, чем отражение - он использует много хитростей, чтобы избежать боли. Это просто оставляет необходимость чего-то вроде:

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

И, конечно же, ваш INotifyPropertyChanged реализация, например:

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;
}
Другие вопросы по тегам