获取对象嵌套结构作为字符串路径

本文关键字:字符串 路径 结构 取对象 嵌套 获取 | 更新日期: 2023-09-27 18:19:31

考虑以下一组类。我想实现两件事。

  1. 获取当前属性的路径的字符串表示形式。例如,totalAsset.BuildingAsset.ThistoricalBuildingAsset.Path应返回"totalAsset.BbuildingAsset.HistoricalBuildingAsset"
  2. 给定路径"TotalAsset.BuildingAsset.HistoricalBuildingAsset"和值"100",我想使用该路径来检索属性并更改其值

代码示例:

public abstract class Field
{
    private string _path = string.Empty;
    public double Value {get;set;}
    public string Path
    {
        get
        {
            //Code probably goes here
            throw new NotImplementedException();
        }
        protected set { _path = value; }
    }
}
public sealed class TotalAsset : Field
{
    public TotalAsset(BuildingAsset buildingAsset)
    {
        Path = "TotalAsset";
        BuildingAsset = buildingAsset;
    }
    public BuildingAsset BuildingAsset { get; private set; }
}
public sealed class BuildingAsset : Field
{
    public HistoricalBuildingAsset HistoricalBuildingAsset { get; private set; }
    public BuildingAsset(HistoricalBuildingAsset historicalBuildingAsset)
    {
        Path = "BuildingAsset";
        this.HistoricalBuildingAsset = historicalBuildingAsset;
    }
}
public sealed class HistoricalBuildingAsset : Field
{
    public HistoricalBuildingAsset()
    {
        Path = "HistoricalBuildingAsset";
    }
}
[TestClass]
public class TestPath
{
    [TestMethod]
    public void MethodTestPath()
    {
        var historicalBuildingAsset = new HistoricalBuildingAsset();
        var buildingAsset = new BuildingAsset(historicalBuildingAsset);
        var totalAsset = new TotalAsset(buildingAsset);
        Assert.AreEqual("TotalAsset.BuildingAsset.HistoricalBuildingAsset", totalAsset.BuildingAsset.HistoricalBuildingAsset.Path);
    }
}

获取对象嵌套结构作为字符串路径

使用多态性不是很容易解决吗?

根据您的问题,您的Path属性似乎有一个不可更改的值,因此您应该能够像以下代码一样解决您的问题:

public class A 
{
     public virtual string Path
     {
         get { return "A"; }
     }
}
public class B : A 
{
     public override string Path
     { 
         get { return base.Path + ".B"; }
     }
}
public class C : B 
{
     public override string Path
     { 
         get { return base.Path + ".C"; }
     }
}
A a = new A();
Console.WriteLine(a.Path); // Prints "A"
B b = new B();
Console.WriteLine(b.Path); // Prints "A.B"
C c = new C();
Console.WriteLine(c.Path); // Prints "A.B.C"

更新v1.1:递归方法(现在包括通过给定的对象路径获取属性值和设置属性值)

因为你想让你的模型保持原样,并采用合成的方式,这是动态获取整个路径的"魔法"。注意,我需要一个新的FullPath属性,以避免在路径计算过程中出现无限循环(您也可以在DotNetFiddle中尝试):

using System;
using System.Linq;
using System.Reflection;
public abstract class Field
{
    public double Value
    {
        get;
        set;
    }
    public string Path
    {
        get;
        protected set;
    }
    public string FullPath
    {
        get
        {
            return BuildPath(this);
        }
    }

    /// <summary>
    /// Recursively-builds a dot-separated full path of associated fields
    /// </summary>
    /// <param name="field">Optional, it's a reference to current associated field </param>
    /// <param name="path">Optional, provided when this method enters to the first associated </param>
    /// <returns>The whole dot-separated full path of associations to Field</returns>
    private string BuildPath(Field field, string path = "")
    {
        // Top-level path won't start with dot
        if (path != string.Empty)
        {
            path += '.';
        }
        path += field.Path;
        // This will look for a property which is of type Field
        PropertyInfo fieldProperty = field.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                                .SingleOrDefault(prop => prop.PropertyType.IsSubclassOf(typeof(Field)));
        // If current field has a property of type Field...
        if (fieldProperty != null)
        {
            // ...we'll get its value and we'll start a recursion to find the next Field.Path
            path = BuildPath((Field)fieldProperty.GetValue(field, null), path);
        }
        return path;
    }
    /// <summary>
    /// Recursively sets a value to an associated field property
    /// </summary>
    /// <param name="path">The whole path to the property</param>
    /// <param name="value">The value to set</param>
    /// <param name="associatedField">Optional, it's a reference to current associated field</param>
    public void SetByPath(string path, object value, Field associatedField = null)
    {
        if (string.IsNullOrEmpty(path.Trim()))
        {
            throw new ArgumentException("Path cannot be null or empty");
        }
        string[] pathParts = path.Split('.');
        if (associatedField == null)
        {
            associatedField = this;
        }
        // This will look for a property which is of type Field
        PropertyInfo property = associatedField.GetType().GetProperty(pathParts[0], BindingFlags.Public | BindingFlags.Instance);
        if (property == null)
        {
            throw new ArgumentException("A property in the path wasn't found", "path");
        }
        object propertyValue = property.GetValue(associatedField, null);
        // If property value isn't a Field, then it's the last part in the path 
        // and it's the property to set
        if (!propertyValue.GetType().IsSubclassOf(typeof(Field)))
        {
            property.SetValue(associatedField, value);
        }
        else
        {
            // ... otherwise, we navigate to the next associated field, removing the first
            // part in the path, so the next call will look for the next property...
            SetByPath(string.Join(".", pathParts.Skip(1)), value, (Field)propertyValue);
        }
    }
    /// <summary>
    /// Recursively gets a value from an associated field property
    /// </summary>
    /// <param name="path">The whole path to the property</param>
    /// <param name="associatedField">Optional, it's a reference to current associated field</param>
    /// <typeparam name="T">The type of the property from which the value is going to be obtained</typeparam>
    public T GetByPath<T>(string path, Field associatedField = null)
    {
        if (string.IsNullOrEmpty(path.Trim()))
        {
            throw new ArgumentException("Path cannot be null or empty");
        }
        string[] pathParts = path.Split('.');
        if (associatedField == null)
        {
            associatedField = this;
        }
        // This will look for a property which is of type Field
        PropertyInfo property = associatedField.GetType().GetProperty(pathParts[0], BindingFlags.Public | BindingFlags.Instance);
        if (property == null)
        {
            throw new ArgumentException("A property in the path wasn't found", "path");
        }
        object propertyValue = property.GetValue(associatedField, null);
        // If property value isn't a Field, then it's the last part in the path 
        // and it's the property to set
        if (!propertyValue.GetType().IsSubclassOf(typeof(Field)))
        {
            return (T)property.GetValue(associatedField, null);
        }
        else
        {
            // ... otherwise, we navigate to the next associated field, removing the first
            // part in the path, so the next call will look for the next property...
            return GetByPath<T>(string.Join(".", pathParts.Skip(1)), (Field)propertyValue);
        }
    }
}
public sealed class TotalAsset : Field
{
    public TotalAsset(BuildingAsset buildingAsset)
    {
        Path = "TotalAsset";
        BuildingAsset = buildingAsset;
    }
    public BuildingAsset BuildingAsset
    {
        get;
        private set;
    }
}
public sealed class BuildingAsset : Field
{
    public HistoricalBuildingAsset HistoricalBuildingAsset
    {
        get;
        private set;
    }
    public BuildingAsset(HistoricalBuildingAsset historicalBuildingAsset)
    {
        Path = "BuildingAsset";
        this.HistoricalBuildingAsset = historicalBuildingAsset;
    }
}
public sealed class HistoricalBuildingAsset : Field
{
    public HistoricalBuildingAsset()
    {
        Path = "HistoricalBuildingAsset";
    }
    public int Age
    {
        get;
        set;
    }
}
public class Program
{
    public static void Main()
    {
        TotalAsset total = new TotalAsset(new BuildingAsset(new HistoricalBuildingAsset()));
        // Prints "TotalAsset.BuildingAsset.HistoricalBuildingAsset"
        Console.WriteLine(total.FullPath);
        total.SetByPath("BuildingAsset.HistoricalBuildingAsset.Age", 300);
        // Prints "300" as expected!
        Console.WriteLine(total.GetByPath<int>("BuildingAsset.HistoricalBuildingAsset.Age"));
    }
}

您可以重用现有的.net框架绑定模式和代码库。你对你想做什么的描述听起来非常像MVVM绑定http://msdn.microsoft.com/en-us/library/ms752347(v=vs.110).aspx.

使用System.Windows.Data.Binding为您提供了一个可扩展的框架,用于使用相对和绝对字符串路径来指定类成员和集合索引,将数据进出对象图。