给定以下类和数据:
public class InnerExample
{
public string Inner1 { get; set; }
}
public class Example
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public List<InnerExample> Inner { get; set; }
}
var a = new Example
{
Property1 = "Foo",
Property2 = "Bar",
Inner = new List<InnerExample>
{
new InnerExample
{
Inner1 = "This is the value to change"
}
}
};
有没有办法通过路径访问最里面的数据?
有什么办法可以说...
a["Inner[0].Inner1"] = "New value"
在这种特殊情况下,我知道我永远不会访问不存在的密钥,因此我并不太担心错误检查。
(抱歉,如果之前有人问过这个问题。我做了一些搜索,但很快就没有关键字可以尝试。)
没有任何内置功能,但可以做到(尽管这并不简单)。
您想要的是向类 Example
添加一个
indexer。在索引器内部,您必须将提供的“属性路径”解析为步骤,并使用 reflection 逐步解析目标属性。
例如,将
Inner[0].Inner1
解析为三个不同的步骤(获取 Inner
,然后从该获取 [0]
,然后从该 Inner1
),您将得到一个类似于以下的循环:
// This works only with plain (non-indexed) properties, no error checking, etc.
object target = this;
PropertyInfo pi = null;
foreach (var step in steps)
{
pi = target.GetType().GetProperty(step);
target = pi.GetValue(target);
}
// And now you can either return target (on a get) or use pi.SetValue (on a set)
感谢您给我的基本建议,乔恩,我想出了一个适合我的案例的解决方案。
我确信有更有效的方法可以做到这一点......我远不是反射专家。
/// <summary>
/// Take an extended key and walk through an object to update it.
/// </summary>
/// <param name="o">The object to update</param>
/// <param name="key">The key in the form of "NestedThing.List[2].key"</param>
/// <param name="value">The value to update to</param>
private static void UpdateModel(object o, string key, object value)
{
// TODO:
// Make the code more efficient.
var target = o;
PropertyInfo pi = null;
// Split the key into bits.
var steps = key.Split('.').ToList();
// Don't walk all the way to the end
// Save that for the last step.
var lastStep = steps[steps.Count-1];
steps.RemoveAt(steps.Count-1);
// Step through the bits.
foreach (var bit in steps)
{
var step = bit;
string index = null;
// Is this an indexed property?
if (step.EndsWith("]"))
{
// Extract out the value of the index
var end = step.IndexOf("[", System.StringComparison.Ordinal);
index = step.Substring(end+1, step.Length - end - 2);
// and trim 'step' back down to exclude it. (List[5] becomes List)
step = step.Substring(0, end);
}
// Get the new target.
pi = target.GetType().GetProperty(step);
target = pi.GetValue(target);
// If the target had an index, find it now.
if (index != null)
{
var idx = Convert.ToInt16(index);
// The most generic way to handle it.
var list = (IEnumerable) target;
foreach (var e in list)
{
if (idx ==0)
{
target = e;
break;
}
idx--;
}
}
}
// Now at the end we can apply the last step,
// actually setting the new value.
if (pi != null || steps.Count == 0)
{
pi = target.GetType().GetProperty(lastStep);
pi.SetValue(target, value);
}
}
这是我制作的更清晰的版本。使用秒表获取时间稍快一些。有时会快 30 毫秒。
/// <summary>
/// Sets the nested property value.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="path">The path.</param>
/// <param name="value">The value.</param>
/// <exception cref="System.ArgumentException">Property '{properties[i]}' not found in type '{currentObject.GetType().FullName}'.</exception>
/// <exception cref="System.ArgumentException">Property '{properties[^1]}' not found in type '{currentObject.GetType().FullName}'.</exception>
public static void SetNestedPropertyValue(object obj, string path, object value) {
string[] properties = path.Split('.');
object currentObject = obj;
// Traverse the property path.
for (int i = 0; i < properties.Length - i; i++) {
var propertyInfo = currentObject.GetType().GetProperty(properties[i]);
if (Object.ReferenceEquals(propertyInfo, null) ) {
throw new ArgumentException($"Property '{properties[i]}' not found in type '{currentObject.GetType().FullName}'.");
}
currentObject = propertyInfo.GetValue(currentObject);
}
var finalProperty = currentObject.GetType().GetProperty(properties[properties.Length - 1]);
if (Object.ReferenceEquals(finalProperty, null) ) {
throw new ArgumentException($"Property '{properties[properties.Length - 1]}' not found in type '{currentObject.GetType().FullName}'.");
}
finalProperty.SetValue(currentObject, value);
}
测试代码:
Stopwatch timer = new Stopwatch();
timer.Start();
UpdateModel(someObjectInstance, error.PropertyName, "test");
timer.Stop();
Debug.WriteLine("Time Taken: " + timer.Elapsed.TotalMilliseconds.ToString("#,##0.00 'milliseconds'"));
timer = new Stopwatch();
timer.Start();
GetNestedPropertyValue(someObjectInstance, error.PropertyName, "test");
timer.Stop();
Debug.WriteLine("Time Taken: " + timer.Elapsed.TotalMilliseconds.ToString("#,##0.00 'milliseconds'"));