繁体   English   中英

使用反射递归获取私有字段值

[英]Recursively get private field value using reflection

我有一个深度嵌套的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");
}
  • 笔记:
    • 有一个关于递归获取属性而不是字段的相关问题。 但即便如此,它也不支持数组字段。
    • 诸如这个用于获取字段的相关问题不是递归的。

该代码适用于您的示例,但如果有字典,您可能需要更改它

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

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

这是执行此操作的方法(这也支持获取数组字段中的某个索引):

完整演示

用法:

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

执行:

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);
    }
          
    object value;
    try
    {
        value = obj.GetType().GetField(current, flags).GetValue(obj);
    }
    catch (NullReferenceException ex)
    {
        throw new Exception($"Wrong path provided: field '{current}' does not exist on '{obj}'");
    }
            
    if (value == null)
    {
        return null;
    }
            
    if (splitted.Length == 1)
    {
        return value;
    }

    if (index != null)
    {
        var asIList = (IList<object>)value;
        if (asIList?.Count >= index.Value)
        {
            value = asIList[index.Value];
        }
        else
        {
            return null;
        }
    }

    return GetFieldValueByPath(value, string.Join(".", splitted.Skip(1)));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM