How to create a dynamic object using composition rather than inheritance

I know that the classic way to create a dynamic object is to inherit from DynamicObject . However, if I already have a class, and I want to add dynamic properties to subclasses, then I'm stuck.

Say I have a ReactiveObject class. And I want to add dynamic properties to it using DynamicObject. Therefore i do it

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

I thought an easy way to do this could be to create an instance of DynamicObject and a proxy for the call.

 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 it will not work because the returned meta object does not know anything about the methods of MyReactiveObject. Is there an easy way to do this without completely redefining DynamicObject.

+5
source share
2 answers

Another possibility is to simply 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 specific use case, which inherits from ReactiveUI.Reactive and has dynamic properties that support the 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; } } } 
+1
source

I came across the following entity.

https://gist.github.com/breezhang/8954586

 public sealed class ForwardingMetaObject : DynamicMetaObject { private readonly DynamicMetaObject _metaForwardee; public ForwardingMetaObject( Expression expression, BindingRestrictions restrictions, object forwarder, IDynamicMetaObjectProvider forwardee, Func<Expression, Expression> forwardeeGetter ) : base(expression, restrictions, forwarder) { // We'll use forwardee meta-object to bind dynamic operations. _metaForwardee = forwardee.GetMetaObject( forwardeeGetter( Expression.Convert(expression, forwarder.GetType()) // [1] ) ); } // Restricts the target object type to TForwarder. // The meta-object we are forwarding to assumes that it gets an instance of TForwarder (see [1]). // We need to ensure that the assumption holds. private DynamicMetaObject AddRestrictions(DynamicMetaObject result) { var restricted = new DynamicMetaObject( result.Expression, BindingRestrictions.GetTypeRestriction(Expression, Value.GetType()).Merge(result.Restrictions), _metaForwardee.Value ); return restricted; } // Forward all dynamic operations or some of them as needed // public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { return AddRestrictions(_metaForwardee.BindGetMember(binder)); } public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { return AddRestrictions(_metaForwardee.BindSetMember(binder, value)); } public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) { return AddRestrictions(_metaForwardee.BindDeleteMember(binder)); } public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { return AddRestrictions(_metaForwardee.BindGetIndex(binder, indexes)); } public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { return AddRestrictions(_metaForwardee.BindSetIndex(binder, indexes, value)); } public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) { return AddRestrictions(_metaForwardee.BindDeleteIndex(binder, indexes)); } public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { return AddRestrictions(_metaForwardee.BindInvokeMember(binder, args)); } public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) { return AddRestrictions(_metaForwardee.BindInvoke(binder, args)); } public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) { return AddRestrictions(_metaForwardee.BindCreateInstance(binder, args)); } public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) { return AddRestrictions(_metaForwardee.BindUnaryOperation(binder)); } public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) { return AddRestrictions(_metaForwardee.BindBinaryOperation(binder, arg)); } public override DynamicMetaObject BindConvert(ConvertBinder binder) { return AddRestrictions(_metaForwardee.BindConvert(binder)); } } 

and so I wrote

 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 { public class Dyno : DynamicObject { private readonly DynamicNotifyingObject _D; public Dyno(DynamicNotifyingObject d) { _D = d; } public override bool TryGetMember(GetMemberBinder binder, out object result) { bool ret = base.TryGetMember(binder, out result); if (ret == false) { result = _D.GetPropertyValue(binder.Name); if (result != null) { ret = true; } } return ret; } public override bool TrySetMember(SetMemberBinder binder, object value) { bool ret = base.TrySetMember(binder, value); if (ret == false) { _D.SetPropertyValue(binder.Name, value); ret = true; } return ret; } } 

And the main object that inherits from ReactiveObject, but we can also add dynamic properties.

  /// <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; [IgnoreDataMember] private Dyno _dynamicObject { get; set; } public Dyno DynamicObject { get { lock (this) { return _dynamicObject ?? (_dynamicObject = new Dyno(this)); } } } #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 #region Public Methods public void AddProperty<T>( string propertyName, T initialValue ) { _dynamicProperties.Add(propertyName, initialValue); _dynamicPropertyTypes.Add(propertyName, typeof(T)); this.RaisePropertyChanged(propertyName); } public void AddProperty<T>( string propertyName) { AddProperty(propertyName, typeof(T)); } public void AddProperty( string propertyName, Type type) { _dynamicProperties.Add(propertyName, null); _dynamicPropertyTypes.Add(propertyName, type); this.RaisePropertyChanged(propertyName); } 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])) { _dynamicProperties[propertyName] = (object) value; this.RaisePropertyChanged(propertyName); } } 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; } #endregion Public Methods DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) { return new ForwardingMetaObject(parameter, BindingRestrictions.Empty, this, DynamicObject, // B meta-object needs to know where to find the instance of B it is operating on. // Assuming that an instance of A is passed to the 'parameter' expression // we get the corresponding instance of B by reading the "B" property. exprA => Expression.Property(exprA, nameof(DynamicObject)) ); } } public static class DynamicNotifyingObjectMixin { public static TRet RaiseAndSetIfChanged<TObj, TRet>(this TObj This, TRet newValue, ref TRet backingField, [CallerMemberName] string property = "") where TObj : DynamicNotifyingObject { if (EqualityComparer<TRet>.Default.Equals(newValue, backingField)) { return newValue; } This.RaisePropertyChanging(property); backingField = newValue; This.RaisePropertyChanged(property); return newValue; } } } 

with test case

 using FluentAssertions; using Xunit; namespace Weingartner.Lens.Spec { public class DynamicNotifyingObjectSpec { class Fixture : DynamicNotifyingObject { public Fixture (): base() { this.AddProperty<string>("A"); this.AddProperty<string>("B"); this.SetPropertyValue("A", "AAA"); this.SetPropertyValue("B", "BBB"); } } [Fact] public void ShouldBeAbleToAddPropertiesLaterOn() { var ff = new Fixture(); ff.AddProperty<string>("newProp"); ff.AddProperty<string>("XXXX"); dynamic f = ff; ff.SetPropertyValue("newProp", "CCC"); ((object)(f.newProp)).Should().Be("CCC"); f.XXXX = "XXXX"; f.newProp = "DDD"; ((object)(f.newProp)).Should().Be("DDD"); ((object)(f.XXXX)).Should().Be("XXXX"); } [Fact] public void ShouldGenerateNotificationOnPropertyChange() { var a = new string []{"A"}; var b = new string []{"B"}; object oa = null; object ob = null; var f = new Fixture(); dynamic fd = f; f.PropertyChanged += (sender, ev) => { dynamic s = sender; oa = sA; ob = sB; }; oa.Should().Be(null); ob.Should().Be(null); fd.A = "A"; oa.Should().Be("A"); ob.Should().Be("BBB"); fd.B = "B"; oa.Should().Be("A"); ob.Should().Be("B"); } } } 
+1
source

Source: https://habr.com/ru/post/1261656/


All Articles