Adding Interface Implementation to ExpandoObject

2.4k views Asked by At

I am passing information between a SQL database and a PLC using 3rd party OPC libraries.

There are essentially two transactions.

Information passed from the PLC to the SQL server is statically typed. Very specific data is captured by the PLC and passed to the SQL database.

Information passed from the SQL server to the PLC is dynamically typed and may be limited to a single property or hundreds.

ITransaction.cs

public interface ITransaction : INotifyPropertyChanged
{
    short Response { get; set; }

    bool Request { get; set; }

    void Reset();
}

BaseTransaction.cs

internal abstract class BaseTransaction<T> : IDisposable
    where T : class, INotifyPropertyChanged
{
    private T _opcClient;

    protected T OpcClient
    {
        get { return _opcClient; }
        set
        {
            if (_opcClient != value)
            {
                OnOpcClientChanging();
                _opcClient = value;
                OnOpcClientChanged();
            }
        }
    }

    protected abstract void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e);

    private void OnOpcClientChanged()
    {
        if (_opcClient != null)
        {
            _opcClient.PropertyChanged += OnOpcClientPropertyChanged;

            OpcManager = new OpcManager(_opcClient);
        }
    }

    private void OnOpcClientChanging()
    {
        if (_opcClient != null)
            _opcClient.PropertyChanged -= OnOpcClientPropertyChanged;
    }
}

StaticTransaction.cs

internal abstract class StaticTransaction<T> : BaseTransaction<T>
    where T : class, ITransaction, new()
{
    public StaticTransaction()
    {
        OpcClient = new T();
    }

    protected override void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "Response":
                ProcessResponse(OpcClient.Response);
                break;
            case "Request":
                ProcessRequest(OpcClient.Request);
                break;
        }
    }
}

DynamicTransaction.cs

internal abstract class DynamicTransaction : BaseTransaction<ExpandoObject>
{
    protected new dynamic OpcClient
    {
        get { return base.OpcClient as dynamic; }
    }

    public DynamicTransaction()
    {
        dynamic opcClient = new ExpandoObject();

        opcClient.Request = false;
        opcClient.Response = 0;

        // Access database, use IDictionary interface to add properties to ExpandoObject.

        opcClient.Reset = new Action(Reset);

        base.OpcClient = opcClient;
    }

    protected override void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "Response":
                ProcessResponse(OpcClient.Response);
                break;
            case "Request":
                ProcessRequest(OpcClient.Request);
                break;
        }
    }

    private void Reset()
    {
        // Use IDictionary interface to reset dynamic properties to defaults.

        OpcClient.Request = false;
        OpcClient.Response = 0;
    }
}

As shown both StaticTransaction and DynamicTransaction have identical implementations of OnOpcClientPropertyChanged among other methods not shown. I would like to bring OnOpcClientPropertyChanged and the other methods into the base class but am prevented from doing so because the base class is unaware of the Response and Request properties found in the OpcClient. Can I bring the interface ITransaction into the base class somehow and still accommodate the dynamic implementation?

1

There are 1 answers

4
Scott Chamberlain On BEST ANSWER

You can subclass DynamicObject (which acts just like ExpandoObject) and make your own version that implements ITransaction. This lets you move the ITransaction constraint up to the base class.

BaseTransaction.cs

internal abstract class BaseTransaction<T> : IDisposable where T : class, ITransaction
{
    private T _opcClient;

    protected T OpcClient
    {
        get { return _opcClient; }
        set
        {
            if (_opcClient != value)
            {
                OnOpcClientChanging();
                _opcClient = value;
                OnOpcClientChanged();
            }
        }
    }


    private void OnOpcClientPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "Response":
                ProcessResponse(OpcClient.Response);
                break;
            case "Request":
                ProcessRequest(OpcClient.Request);
                break;
        }
    }

    protected abstract void ProcessResponse(short opcClientResponse);

    protected abstract void ProcessRequest(bool opcClientRequest);

    private void OnOpcClientChanged()
    {
        if (_opcClient != null)
        {
            _opcClient.PropertyChanged += OnOpcClientPropertyChanged;

            OpcManager = new OpcManager(_opcClient);
        }
    }

    private void OnOpcClientChanging()
    {
        if (_opcClient != null)
            _opcClient.PropertyChanged -= OnOpcClientPropertyChanged;
    }
}

StaticTransaction.cs

internal abstract class StaticTransaction<T> : BaseTransaction<T>
where T : class, ITransaction, new()
{
    public StaticTransaction()
    {
        OpcClient = new T();
    }
}

DynamicTransactionObject.cs

internal class DynamicTransactionObject : DynamicObject, ITransaction, IDictionary<string, object>
{

    private readonly Dictionary<string, object> _data = new Dictionary<string, object>();

    public DynamicTransactionObject()
    {
        //Set initial default values for the two properties to populate the entries in the dictionary.
        _data[nameof(Response)] = default(short);
        _data[nameof(Request)] = default(bool);
    }

    public short Response
    {
        get
        {
            return (short)_data[nameof(Response)];
        }
        set
        {
            if (Response.Equals(value))
                return;

            _data[nameof(Response)] = value;
            OnPropertyChanged();
        }
    }

    public bool Request
    {
        get
        {
            return (bool)_data[nameof(Request)];
        }
        set
        {
            if (Request.Equals(value))
                return;

            _data[nameof(Request)] = value;
            OnPropertyChanged();
        }
    }

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return _data.Keys;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {

        return _data.TryGetValue(binder.Name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        object oldValue;
        _data.TryGetValue(binder.Name, out oldValue)
        _data[binder.Name] = value;
        if(!Object.Equals(oldValue, value)
           OnPropertyChanged(binder.Name);
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #region IDictionary<string,object> members
    IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)_data).GetEnumerator();
    }

    void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
    {
        ((ICollection<KeyValuePair<string, object>>)_data).Add(item);
    }

    void ICollection<KeyValuePair<string, object>>.Clear()
    {
        _data.Clear();
    }

    bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
    {
        return ((ICollection<KeyValuePair<string, object>>)_data).Contains(item);
    }

    void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
    {
        ((ICollection<KeyValuePair<string, object>>)_data).CopyTo(array, arrayIndex);
    }

    bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
    {
        return ((ICollection<KeyValuePair<string, object>>)_data).Remove(item);
    }

    int ICollection<KeyValuePair<string, object>>.Count
    {
        get { return _data.Count; }
    }

    bool ICollection<KeyValuePair<string, object>>.IsReadOnly
    {
        get { return ((ICollection<KeyValuePair<string, object>>)_data).IsReadOnly; }
    }

    bool IDictionary<string, object>.ContainsKey(string key)
    {
        return _data.ContainsKey(key);
    }

    void IDictionary<string, object>.Add(string key, object value)
    {
        _data.Add(key, value);
    }

    bool IDictionary<string, object>.Remove(string key)
    {
        return _data.Remove(key);
    }

    bool IDictionary<string, object>.TryGetValue(string key, out object value)
    {
        return _data.TryGetValue(key, out value);
    }

    object IDictionary<string, object>.this[string key]
    {
        get { return _data[key]; }
        set { _data[key] = value; }
    }

    ICollection<string> IDictionary<string, object>.Keys
    {
        get { return _data.Keys; }
    }

    ICollection<object> IDictionary<string, object>.Values
    {
        get { return _data.Values; }
    }
#endregion
}

DynamicTransaction.cs

internal abstract class DynamicTransaction : BaseTransaction<DynamicTransactionObject>
{
    protected new dynamic OpcClient
    {
        get { return base.OpcClient as dynamic; }
    }

    public DynamicTransaction()
    {
        var opcClient = new DynamicTransactionObject();

        // Access database, use IDictionary<string,object> interface to add properties to DynamicObject.

        base.OpcClient = opcClient;
    }
}