通过反射解析字符串路径,如 WPF 绑定
Resolving string path through reflection, like WPF binding
我需要能够像 WPF 绑定一样将(字符串)路径解析为 属性 或 class。
所以类似于“MyProperty.MySubProperty.MyList[2].FinalProperty”。
我不需要订阅更改,我只需要解析一次就可以得到值。
是否有任何我可以重用 WPF 框架的代码,或任何其他已经存在的代码?
I need to be able to resolve a (string) path to a property of a class, like WPF binding does. So something like "MyProperty.MySubProperty.MyList[2].FinalProperty". I do not need to subscribe to changes, I just need to resolve it once to get the value.
完全有可能得到这样的任何路径的值。在不知道价值观的情况下,我把一些可能会有所帮助的东西放在一起。
我不是反射专家,它根本没有错误处理,所以如果满足你的需要,你需要修改和扩展它。
有一点需要注意,我无法弄清楚如何在没有对象引用(开头的 StartClass start = new StartClass();
)的情况下解析路径。我将把它留作 reader.
的练习
class Program
{
public static void Main()
{
StartClass start = new StartClass();
string path = "MyProperty.MySubProperty.MyList[2].FinalProperty";
string[] props = path.Split('.');
var propertyValue = GetPropertyValue(start, props[0]);
for (int i = 1; i < props.Length; i++)
{
ref string prop = ref props[i];
// check to see if the property is an indexed property
if (prop.Contains("[") && prop.Contains("]"))
{
// split MyList[2] into MyList 2]
string[] split = prop.Split("[");
// get the value of MyList
propertyValue = GetPropertyValue(propertyValue, split[0]);
// get the number in 2]
string rawIndex = split[1].Replace("]", "");
// parse the number to an actual number
if (int.TryParse(rawIndex, out int index))
{
// make sure the property is an enumerable, wouldn't make much sense to use an index on it if it wasn't
if (propertyValue is IEnumerable enumerable)
{
propertyValue = enumerable.Cast<object>().ElementAt(index);
}
else
{
throw new NotSupportedException("Attempted to index non-enumerable object");
}
}
else
{
throw new NotSupportedException("Index isn't integer, ranges supported at this time.");
}
}
else
{
// if the property wasn't an indexed property, just get the next value of the next property
propertyValue = GetPropertyValue(propertyValue, prop);
}
}
Console.WriteLine(propertyValue);
// outputs: 12
}
public static object GetPropertyValue(object property, string PropertyName)
{
var propertyInfo = property.GetType().GetProperty(PropertyName);
return propertyInfo.GetValue(property);
}
}
public class StartClass
{
public A MyProperty { get; set; } = new();
}
public class A
{
public B MySubProperty { get; set; } = new B();
}
public class B
{
public List<C> MyList { get; set; } = new List<C>() {
new C(),
new C(),
new C(),
};
}
public class C
{
public int FinalProperty { get; set; } = 12;
}
如果需要解析完全100%等价于绑定,那么最好使用带绑定的简单代理。
这是我对这种代理的简单实现。
在里面,除了可以得到属性的值,还可以监听它的变化,看看是否设置了绑定,处于什么状态。
using System;
using System.Windows;
using System.Windows.Data;
namespace Proxy
{
/// <summary> Provides a <see cref="DependencyObject"/> proxy with
/// one property and an event notifying about its change. </summary>
public class ProxyDO : DependencyObject
{
/// <summary> Property for setting external bindings. </summary>
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(object), typeof(ProxyDO), new PropertyMetadata(null));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
ValueChanged?.Invoke(this, e);
}
/// <summary> An event that occurs when the value of any
/// <see cref="DependencyProperty"/> of this object changes.</summary>
public event EventHandler<DependencyPropertyChangedEventArgs> ValueChanged;
/// <summary> Returns <see langword="true"/> if the property value <see cref="Value"/> is not set.</summary>
public bool IsUnsetValue => Equals(ReadLocalValue(ValueProperty), DependencyProperty.UnsetValue);
/// <summary> Clears all <see cref="DependencyProperty"/> this <see cref="ProxyDO"/>.</summary>
public void Reset()
{
LocalValueEnumerator locallySetProperties = GetLocalValueEnumerator();
while (locallySetProperties.MoveNext())
{
DependencyProperty propertyToClear = locallySetProperties.Current.Property;
if (!propertyToClear.ReadOnly)
{
ClearValue(propertyToClear);
}
}
}
/// <summary> <see langword="true"/> if the property <see cref="Value"/> has Binding.</summary>
public bool IsValueBinding => BindingOperations.GetBindingExpressionBase(this, ValueProperty) != null;
/// <summary> <see langword="true"/> if the property <see cref="Value"/> has a binding
/// and it is in the state <see cref="BindingStatus.Active"/>.</summary>
public bool IsActiveValueBinding
{
get
{
var exp = BindingOperations.GetBindingExpressionBase(this, ValueProperty);
if (exp == null)
return false;
var status = exp.Status;
return status == BindingStatus.Active;
}
}
/// <summary>Setting the Binding to the Property <see cref="Value"/>.</summary>
/// <param name="binding">The binding to be assigned to the property.</param>
public void SetValueBinding(BindingBase binding)
=> BindingOperations.SetBinding(this, ValueProperty, binding);
}
}
使用代理的例子:
ProxyDO proxy;
public void MainMetod()
{
// Create Proxy.
proxy = new ProxyDO();
//An example of adding a wiretapping method, if necessary.
proxy.ValueChanged += OnValueChanged;
// Setting Binding.
string propertyPath = "All string";
proxy.SetValueBinding(new Binding(propertyPath));
object currentValue = proxy.Value;
}
private void OnValueChanged(object sender, DependencyPropertyChangedEventArgs e)
{
//Listening code
}
P.S. 以非常简化的形式显示。
创建绑定时,为了正确操作,您必须指定除路径之外的其他参数。
至少 Source 是一个对象,根据指定的 属性 路径,将搜索此源 属性。
我需要能够像 WPF 绑定一样将(字符串)路径解析为 属性 或 class。 所以类似于“MyProperty.MySubProperty.MyList[2].FinalProperty”。 我不需要订阅更改,我只需要解析一次就可以得到值。
是否有任何我可以重用 WPF 框架的代码,或任何其他已经存在的代码?
I need to be able to resolve a (string) path to a property of a class, like WPF binding does. So something like "MyProperty.MySubProperty.MyList[2].FinalProperty". I do not need to subscribe to changes, I just need to resolve it once to get the value.
完全有可能得到这样的任何路径的值。在不知道价值观的情况下,我把一些可能会有所帮助的东西放在一起。
我不是反射专家,它根本没有错误处理,所以如果满足你的需要,你需要修改和扩展它。
有一点需要注意,我无法弄清楚如何在没有对象引用(开头的 StartClass start = new StartClass();
)的情况下解析路径。我将把它留作 reader.
class Program
{
public static void Main()
{
StartClass start = new StartClass();
string path = "MyProperty.MySubProperty.MyList[2].FinalProperty";
string[] props = path.Split('.');
var propertyValue = GetPropertyValue(start, props[0]);
for (int i = 1; i < props.Length; i++)
{
ref string prop = ref props[i];
// check to see if the property is an indexed property
if (prop.Contains("[") && prop.Contains("]"))
{
// split MyList[2] into MyList 2]
string[] split = prop.Split("[");
// get the value of MyList
propertyValue = GetPropertyValue(propertyValue, split[0]);
// get the number in 2]
string rawIndex = split[1].Replace("]", "");
// parse the number to an actual number
if (int.TryParse(rawIndex, out int index))
{
// make sure the property is an enumerable, wouldn't make much sense to use an index on it if it wasn't
if (propertyValue is IEnumerable enumerable)
{
propertyValue = enumerable.Cast<object>().ElementAt(index);
}
else
{
throw new NotSupportedException("Attempted to index non-enumerable object");
}
}
else
{
throw new NotSupportedException("Index isn't integer, ranges supported at this time.");
}
}
else
{
// if the property wasn't an indexed property, just get the next value of the next property
propertyValue = GetPropertyValue(propertyValue, prop);
}
}
Console.WriteLine(propertyValue);
// outputs: 12
}
public static object GetPropertyValue(object property, string PropertyName)
{
var propertyInfo = property.GetType().GetProperty(PropertyName);
return propertyInfo.GetValue(property);
}
}
public class StartClass
{
public A MyProperty { get; set; } = new();
}
public class A
{
public B MySubProperty { get; set; } = new B();
}
public class B
{
public List<C> MyList { get; set; } = new List<C>() {
new C(),
new C(),
new C(),
};
}
public class C
{
public int FinalProperty { get; set; } = 12;
}
如果需要解析完全100%等价于绑定,那么最好使用带绑定的简单代理。
这是我对这种代理的简单实现。 在里面,除了可以得到属性的值,还可以监听它的变化,看看是否设置了绑定,处于什么状态。
using System;
using System.Windows;
using System.Windows.Data;
namespace Proxy
{
/// <summary> Provides a <see cref="DependencyObject"/> proxy with
/// one property and an event notifying about its change. </summary>
public class ProxyDO : DependencyObject
{
/// <summary> Property for setting external bindings. </summary>
public object Value
{
get { return (object)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(object), typeof(ProxyDO), new PropertyMetadata(null));
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
ValueChanged?.Invoke(this, e);
}
/// <summary> An event that occurs when the value of any
/// <see cref="DependencyProperty"/> of this object changes.</summary>
public event EventHandler<DependencyPropertyChangedEventArgs> ValueChanged;
/// <summary> Returns <see langword="true"/> if the property value <see cref="Value"/> is not set.</summary>
public bool IsUnsetValue => Equals(ReadLocalValue(ValueProperty), DependencyProperty.UnsetValue);
/// <summary> Clears all <see cref="DependencyProperty"/> this <see cref="ProxyDO"/>.</summary>
public void Reset()
{
LocalValueEnumerator locallySetProperties = GetLocalValueEnumerator();
while (locallySetProperties.MoveNext())
{
DependencyProperty propertyToClear = locallySetProperties.Current.Property;
if (!propertyToClear.ReadOnly)
{
ClearValue(propertyToClear);
}
}
}
/// <summary> <see langword="true"/> if the property <see cref="Value"/> has Binding.</summary>
public bool IsValueBinding => BindingOperations.GetBindingExpressionBase(this, ValueProperty) != null;
/// <summary> <see langword="true"/> if the property <see cref="Value"/> has a binding
/// and it is in the state <see cref="BindingStatus.Active"/>.</summary>
public bool IsActiveValueBinding
{
get
{
var exp = BindingOperations.GetBindingExpressionBase(this, ValueProperty);
if (exp == null)
return false;
var status = exp.Status;
return status == BindingStatus.Active;
}
}
/// <summary>Setting the Binding to the Property <see cref="Value"/>.</summary>
/// <param name="binding">The binding to be assigned to the property.</param>
public void SetValueBinding(BindingBase binding)
=> BindingOperations.SetBinding(this, ValueProperty, binding);
}
}
使用代理的例子:
ProxyDO proxy;
public void MainMetod()
{
// Create Proxy.
proxy = new ProxyDO();
//An example of adding a wiretapping method, if necessary.
proxy.ValueChanged += OnValueChanged;
// Setting Binding.
string propertyPath = "All string";
proxy.SetValueBinding(new Binding(propertyPath));
object currentValue = proxy.Value;
}
private void OnValueChanged(object sender, DependencyPropertyChangedEventArgs e)
{
//Listening code
}
P.S. 以非常简化的形式显示。 创建绑定时,为了正确操作,您必须指定除路径之外的其他参数。 至少 Source 是一个对象,根据指定的 属性 路径,将搜索此源 属性。