获取对象嵌套结构作为字符串路径
本文关键字:字符串 路径 结构 取对象 嵌套 获取 | 更新日期: 2023-09-27 18:19:31
考虑以下一组类。我想实现两件事。
- 获取当前属性的路径的字符串表示形式。例如,totalAsset.BuildingAsset.ThistoricalBuildingAsset.Path应返回"totalAsset.BbuildingAsset.HistoricalBuildingAsset"
- 给定路径"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为您提供了一个可扩展的框架,用于使用相对和绝对字符串路径来指定类成员和集合索引,将数据进出对象图。