Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a dynamic object via composition rather than inheritence

Tags:

c#

dynamic

I am aware that the classic way to create a dynamic object is to inherit from DynamicObject. However if I already have a class and I wish to add dynamic properties to subclasses of that then I am stuck.

Say I have a class ReactiveObject And I wish to add dynamic properties to it using DynamicObject. So I do this

public class MyReactiveObject : ReactiveObject,  IDynamicMetaObjectProvider{
    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
         ...
    }
}

I thought the easy way to do this might be to create an instance of DynamicObject and proxy the call to that.

public class MyDynamicObject : DynamicObject{}
public class MyReactiveObject : ReactiveObject,  IDynamicMetaObjectProvider{
    MyDynamicObject DynamicObject = new MyDynamicObject();
    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
         return this.DynamicObject.GetMetaObject(parameter);
    }
}

except that is not going to work because the returned meta object doesn't know anything about the methods on MyReactiveObject. Is there any easy way to do this without fully reimplementing DynamicObject.

like image 894
bradgonesurfing Avatar asked Oct 29 '22 15:10

bradgonesurfing


1 Answers

Another possibility is just to use this library

https://github.com/remi/MetaObject

using System;
using System.Dynamic;

public class MyClass : Whatever, IDynamicMetaObjectProvider {

    // This 1 line is *ALL* you need to add support for all of the DynamicObject methods
    public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression e)
        { return new MetaObject(e, this); }

    // Now, if you want to handle dynamic method calls, 
    // you can implement TryInvokeMember, just like you would in DynamicObject!
    public bool TryInvokeMember
       (InvokeMemberBinder binder, object[] args, out object result) {
        if (binder.Name.Contains("Cool")) {
            result = "You called a method with Cool in the name!";
            return true;
        } else {
            result = null;
            return false;
        }
    }
}

and for my particular use case which is inheriting from ReactiveUI.Reactive object and having dynamic properties which support INPC I generated

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using ReactiveUI;

namespace Weingartner.Lens
{
    /// <summary>
    /// An object you can add properties to at runtime which raises INPC events when those
    /// properties are changed.
    /// </summary>
    [DataContract]
    public class DynamicNotifyingObject : ReactiveObject, IDynamicMetaObjectProvider
    {
        #region Private Members

        [DataMember]
        private Dictionary<string, object> _DynamicProperties;
        [DataMember]
        private Dictionary<string, Type> _DynamicPropertyTypes;

        #endregion Private Members

        #region Constructor

        public DynamicNotifyingObject() : this(new Tuple<string,Type>[] { }) { }

        public DynamicNotifyingObject(IEnumerable<Tuple<string,Type>> propertyNames)
        {
            if (propertyNames == null)
            {
                throw new Exception("propertyNames is empty");
            }

            _DynamicProperties = new Dictionary<string, object>();
            _DynamicPropertyTypes = new Dictionary<string, Type>();
            foreach ( var prop in propertyNames )
            {
                AddProperty(prop.Item1, prop.Item2);
            }
        }
        #endregion Constructor

        public void AddProperty<T>( string propertyName, T initialValue )
        {
            _DynamicProperties.Add(propertyName, initialValue);
            _DynamicPropertyTypes.Add(propertyName, typeof(T));
            this.RaisePropertyChanged(propertyName);
        }

        /// <summary>
        /// Set the property. Will throw an exception if the property does not exist. 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyName"></param>
        /// <param name="raw"></param>
        public void SetPropertyValue<T>(string propertyName, T raw)
        {
            if (!_DynamicProperties.ContainsKey(propertyName))
            {
                throw new ArgumentException(propertyName + " property does not exist on " + GetType().Name);
            }

            var converter = DynamicLens2INPC.CreateConverter<T>(raw.GetType(), _DynamicPropertyTypes[propertyName]);
            var value = converter(raw);

            if (!value.Equals(_DynamicProperties[propertyName]))
            {
                this.RaisePropertyChanging(propertyName);
                _DynamicProperties[propertyName] = (object) value;
                this.RaisePropertyChanged(propertyName);
            }
        }
        /// <summary>
        /// Get the property. Will throw an exception if the property does not exist.
        /// </summary>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public object GetPropertyValue(string propertyName)
        {
            if (!_DynamicProperties.ContainsKey(propertyName))
            {
                throw new ArgumentException(propertyName + " property does not exist " + GetType().Name);
            }
            return _DynamicProperties.ContainsKey(propertyName) ? _DynamicProperties[propertyName] : null;
        }

        public bool HasDynamicProperty(string propertyName) => _DynamicProperties.ContainsKey(propertyName);

        DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression e) { return new MetaObject(e, this); }

        /// <summary>
        /// Support for MetaObject. See https://github.com/remi/MetaObject 
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (HasDynamicProperty(binder.Name))
            {
                result = GetPropertyValue(binder.Name);
                return true;
            }

            // This path will return any real properties on the object
            result = null;
            return false;
        }

        /// <summary>
        /// Support for MetaObject. See https://github.com/remi/MetaObject
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (HasDynamicProperty(binder.Name))
            {
                SetPropertyValue(binder.Name, value);
                return true;
            }

            // This path will try to set any real properties on the object
            return false;
        }

    }
}
like image 178
bradgonesurfing Avatar answered Nov 14 '22 07:11

bradgonesurfing