将Object嵌套结构作为字符串路径获取

时间:2014-08-26 13:36:14

标签: c# .net

考虑以下一组课程。我想要实现两件事。

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

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

2 个答案:

答案 0 :(得分:3)

使用多态可以轻松解决这个问题吗?

根据您的问题,您的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:递归方法(现在包括获取属性值并按给定对象路径设置属性值)

因为您希望将模型保留为并使用合成方式,这就是" magic"动态获取整个路径。请注意,我需要一个新的FullPath属性,以避免在路径计算过程中出现无限循环(you can also try it in a 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"));
    }
}

答案 1 :(得分:0)

您可以重用现有的.net框架绑定模式和代码库。你对你想要做什么的描述听起来像MVVM绑定到我身上。这里解释了在WPF中使用绑定http://msdn.microsoft.com/en-us/library/ms752347(v=vs.110).aspx

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