【问题标题】:Resolving string path through reflection, like WPF binding通过反射解析字符串路径,如 WPF 绑定
【发布时间】:2021-05-21 07:21:52
【问题描述】:

我需要能够解析到类属性的(字符串)路径,就像 WPF 绑定一样。 所以类似于“MyProperty.MySubProperty.MyList[2].FinalProperty”。 我不需要订阅更改,我只需要解决一次即可获得价值。

有没有我可以重用 WPF 框架的代码,或者其他已经存在的代码?

【问题讨论】:

  • 刚刚找到了一些接近的问题/答案,但如果可能的话,我希望拥有完整的 WPF 绑定语法。如果可能的话,不要重新发明轮子……stackoverflow.com/questions/18870162/…stackoverflow.com/questions/1954746/…
  • 也会对解决方案感兴趣。即使你有一个BindingExpression,我也找不到不使用属性名称字符串来获取源属性的方法。
  • 绑定路径使用专用类型进行保存和操作:PropertyPath。它的大部分方法都是internal,而且它的实现和使用方法完全不容易理解。编写自己的实现来使用递归从路径解析具体属性要容易得多。

标签: c# wpf reflection


【解决方案1】:

我需要能够解析类属性的(字符串)路径,就像 WPF 绑定一样。所以类似于“MyProperty.MySubProperty.MyList[2].FinalProperty”。我不需要订阅更改,我只需要解决一次即可获得价值。

完全有可能获得任何这样的路径的值。在不了解价值观的情况下,我将一些可能会有所帮助的东西放在一起。

我不是反射专家,它根本没有错误处理,所以如果满足你的需要,你需要修改和扩展它。

需要注意的是,我无法弄清楚如何在没有对象引用的情况下解析路径(开头的 StartClass start = new StartClass();)。我将把它作为练习留给读者。

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

【讨论】:

  • 不是问题我希望它是适合您问题的正确解决方案!确保添加大量错误处理,此代码使 ALOT 假设值在正确的位置。
【解决方案2】:

如果你需要解析完全 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是一个对象,在该对象中,会根据指定的属性路径,搜索这个源属性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-08
    • 2016-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多