如何通过合成而不是继承来创建动态对象

bra*_*ing 5 c# dynamic

我知道创建动态对象的经典方法是从DynamicObject继承.但是,如果我已经有一个类,并且我希望将动态属性添加到其中,那么我就会陷入困境.

假设我有一个类ReactiveObject我希望使用DynamicObject为它添加动态属性.所以我这样做

public class MyReactiveObject : ReactiveObject,  IDynamicMetaObjectProvider{
    public DynamicMetaObject GetMetaObject(Expression parameter)
    {
         ...
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为这样做的简单方法可能是创建一个DynamicObject实例并代理对它的调用.

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

除了不起作用,因为返回的元对象对MyReactiveObject上的方法一无所知.有没有完全重新实现DynamicObject的简单方法.

bra*_*ing 2

另一种可能性是只使用这个库

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于我的特定用例,它继承自 ReactiveUI.Reactive 对象并具有支持我生成的 INPC 的动态属性

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

    }
}
Run Code Online (Sandbox Code Playgroud)