Here's the way to do it (note the improvement over other answers, achieved using regex to prepare the path-parts ahead of time):
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;
}
Here's the definition of the Field
helper class:
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;
}
}
Usage (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"));
}