使用反射递归获取私有字段值
Posted
技术标签:
【中文标题】使用反射递归获取私有字段值【英文标题】:Recursively get private field value using reflection 【发布时间】:2021-12-07 17:20:20 【问题描述】:我有一个深度嵌套的 private
字段链,我想对其进行递归迭代以获取某个目标字段的值。
如何做到这一点?
例如:
public class A
private B b;
public A(B b) this.b = b;
public class B
private C[] cItems;
public B(C[] cItems) this.cItems = cItems;
public class C
private string target; // <-- get this value
public C(int target) this.target = val;
public static void GetFieldValueByPath(object targetObj, string targetFieldPath)
// how to do it? I self-answer below
用法如下:
public void DoSomething(A a)
var val = GetFieldValueByPath(a, "b.cItems[2].target");
注意事项:
有一个关于recursively getting properties 的相关问题,但不是字段。但即便如此,它也不支持数组字段。
获取字段的this one等相关问题不是递归的。
【问题讨论】:
虽然这是可以做到的,但我强烈建议不要这样做。我看到您需要这样做的唯一原因是,如果 A 是在您没有源代码的第三方库中定义的。 @NigelBess,这完全是我的用例,我实际上需要get Serilog's log file path location 进行调试。 【参考方案1】:这是执行此操作的方法(请注意对其他答案的改进,使用regex 提前准备路径部分):
public static object GetFieldValueByPath(object obj, string fieldPath)
var flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var parts = Regex.Matches(fieldPath, @"([^.\[]+)(?:\[(.*?)\])?").Cast<Match>().Select(match => match.Groups).ToList();
return GetFieldValueByPathParts(obj, parts, flags);
private static object GetFieldValueByPathParts(object obj, List<GroupCollection> parts, BindingFlags flags)
if (obj == null || parts.Count == 0) return obj;
var field = new Field(name: parts[0][1].Value, value: (object)null, index: parts[0][2].Value);
try
field.Value = obj.GetType().GetField(field.Name, flags).GetValue(obj);
catch (NullReferenceException ex)
throw new Exception($"Wrong path provided: field 'field.Name' does not exist on 'obj'");
field = TrySetEnumerableValue(field);
return GetFieldValueByPathParts(field.Value, parts.Skip(1).ToList(), flags);
private static Field TrySetEnumerableValue(Field field)
if (field.Value != null && field.Index != null)
var enumerable = ((IEnumerable)field.Value).Cast<object>();
field.Value = field.Index <= enumerable.Count() ? enumerable.ElementAt(field.Index.Value) : null;
return field;
这里是 Field
助手类的定义:
public class Field
public string Name get; set;
public object Value get; set;
public int? Index get; set;
public Field(string name, object value, string index)
Name = name;
Value = value;
Index = int.TryParse(index, out int parsed) ? parsed : (int?)null;
用法(live demo):
public static void Main(string[] s)
var a1 = new A(new B(new C[] new C(1), new C(2), new C(3) ) );
Console.WriteLine(GetFieldValueByPath(a1, "b.cItems[2].target"));
var a2 = new A(new B(new C[] ) );
Console.WriteLine(GetFieldValueByPath(a2, "b.cItems[2].target"));
var a3 = new A(new B(null) );
Console.WriteLine(GetFieldValueByPath(a3, "b.cItems[2].target"));
【讨论】:
【参考方案2】:OfirD 的答案是正确的,但它不会起作用。不仅不编译,C[] 也没有实现IList<object>
。
它也有很多它没有考虑的场景。 (我没有更新他的代码来解决这些情况)
如果数组没有被整数索引怎么办? 如果是锯齿状数组怎么办? 如果路径指向属性而不是字段怎么办?我已经更新了他的代码来工作:
public static object GetFieldValueByPath(object obj, string fieldPath)
var flags =
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var splitted = fieldPath.Split('.');
var current = splitted[0];
int? index = null;
// Support getting a certain index in an array field
var match = Regex.Match(current, @"\[([0-9]+)\]");
if (match.Groups.Count > 1)
current = fieldPath.Substring(0, match.Groups[0].Index);
index = int.Parse(match.Groups[1].Value);
var value = obj.GetType().GetField(current, flags).GetValue(obj);
if (value == null)
return null;
if (splitted.Length == 1)
return value;
if (index != null)
value = Index(value, index.Value);
return GetFieldValueByPath(value, string.Join(".", splitted.Skip(1)));
static object Index(object obj, int index)
var type = obj.GetType();
foreach (var property in obj.GetType().GetProperties())
var indexParams = property.GetIndexParameters();
if (indexParams.Length != 1) continue;
return property.GetValue(obj, new object[] index );
throw new Exception($"type has no getter of the format type[int]");
【讨论】:
不要使用违反Code of Conduct 的语言,您可以评论说我忘了用Count
替换Length
。毕竟,我的代码显然适用于我的演示。至于你的其他cmets:(1)锯齿状数组:你的代码不起作用,在我的demo上运行看看。 (2) 指向属性的路径:这不是这个问题的主题,因为在我的问题中已经有关于属性的问题。 (3) 我猜你的代码闻起来和我的一样,因为你几乎复制了它。【参考方案3】:
代码适用于您的示例,但如果有字典,您可能需要更改它
public static object GetFieldValueByPath(object targetObj, string targetFieldPath)
var fieldNames = targetFieldPath.Split('.');
var type = targetObj.GetType();
foreach (var fieldName in fieldNames)
string name = fieldName;
int? objectIndex = default;
if (name.Contains('['))//getting fieldName without indexer
int indexerStart = name.IndexOf('[');
int indexerEnd = name.IndexOf(']');
objectIndex = int.Parse(name.Substring(indexerStart + 1, indexerEnd-indexerStart - 1));
name = name.Substring(0, indexerStart);
var field = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (objectIndex.HasValue)//here we know that field is collection
targetObj=((IList<object>)field.GetValue(targetObj))[0];//getting item by index
type = targetObj.GetType();
else
targetObj = field.GetValue(targetObj);
type = field.FieldType;
return targetObj;
【讨论】:
请尝试使用我在回答中提供的演示运行您的代码,因为它目前有几个问题,最突出的是它不是递归的。以上是关于使用反射递归获取私有字段值的主要内容,如果未能解决你的问题,请参考以下文章