我知道创建动态对象的经典方法是从 DynamicObject 继承。但是,如果我已经有一个类,并且我希望将动态属性添加到其子类中,那么我就会被卡住。
假设我有一个类 ReactiveObject 我希望使用DynamicObject为它添加动态属性。所以我这样做
public class MyReactiveObject : ReactiveObject, IDynamicMetaObjectProvider{
public DynamicMetaObject GetMetaObject(Expression parameter)
{
...
}
}
我认为这样做的简单方法可能是创建一个DynamicObject实例并代理对它的调用。
public class MyDynamicObject : DynamicObject{}
public class MyReactiveObject : ReactiveObject, IDynamicMetaObjectProvider{
MyDynamicObject DynamicObject = new MyDynamicObject();
public DynamicMetaObject GetMetaObject(Expression parameter)
{
return this.DynamicObject.GetMetaObject(parameter);
}
}
除了不起作用,因为返回的元对象对MyReactiveObject上的方法一无所知。如果没有完全重新实现DynamicObject,有没有简单的方法。
答案 0 :(得分:1)
我遇到了以下要点。
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));
}
}
所以我写了
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;
}
}
继承自ReactiveObject的主要对象,但我们也可以添加动态属性。
/// <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;
}
}
}
带有测试用例
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");
}
}
}
答案 1 :(得分:1)
另一种可能性就是使用这个库
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;
}
}
}
以及我的特定用例,它继承自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;
}
}
}