通过反射解析字符串路径,如 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 是一个对象,根据指定的 属性 路径,将搜索此源 属性。