考虑以下一组课程。我想要实现两件事。
代码示例:
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);
}
}
答案 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"
因为您希望将模型保留为并使用合成方式,这就是" 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为您提供了一个可扩展的框架,用于使用相对和绝对字符串路径来获取数据进出对象图,以指定类成员和集合索引。