2016-12-21 9 views
5

Mam świadomość, że klasyczny sposób tworzenia dynamicznego obiektu to dziedziczenie po DynamicObject. Jednak jeśli już mam klasę i chcę dodać właściwości dynamiczne do podklas tego, to utknąłem.Jak utworzyć obiekt dynamiczny za pomocą kompozycji, a nie dziedziczenia?

Powiedz, że mam klasę ReactiveObject I chcę dodać do niej właściwości dynamiczne za pomocą DynamicObject. Więc robię to

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

Myślałem łatwy sposób to zrobić mogłoby być stworzenie instancji DynamicObject i pełnomocnika wezwanie do tego.

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

z wyjątkiem, że nie będzie działać, ponieważ zwracany obiekt meta nie wie nic o metodach na MyReactiveObject. Czy istnieje prosty sposób, aby to zrobić bez pełnego reimplementacji DynamicObject.

Odpowiedz

1

Inną możliwością jest po prostu użyć tej biblioteki

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

i dla mojego konkretnego przypadku użycia, która dziedziczy z ReactiveUI. Obiekt reaktywny i posiadający właściwości dynamiczne, które wspierają wygenerowane 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; 
     } 

    } 
} 
1

Natknąłem się na następujący sens.

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's meta-object to bind dynamic operations. 
     _metaForwardee = forwardee.GetMetaObject(
      forwardeeGetter(
       Expression.Convert(expression, forwarder.GetType()) // [1] 
      ) 
     ); 
    } 

    // Restricts the target object's 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)); 
    } 


} 

a więc napisałem

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

i główny obiekt, który dziedziczy ReactiveObject ale możemy również dodać właściwości dynamicznych.

/// <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's 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; 
     } 
    } 
} 

z testu

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 = s.A; 
       ob = s.B; 
      }; 

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


    } 
} 
Powiązane problemy