Linq 反射表达式在 属性 属性上抛出异常

Linq Reflection Expression throws exception on property of proprety

我正在尝试使用 Linq 表达式参数创建函数,但遇到了一个问题。

这是我的测试函数:

public static void OutputField(Person p, Expression<Func<Person, string>> outExpr)
{
    var expr = (MemberExpression)outExpr.Body;
    var prop = (PropertyInfo)expr.Member;
    string v = prop.GetValue(p, null).ToString();
    Console.WriteLine($"value={v}");
}

如果我将一个 Person 对象定义为

public class Person
{
    public PersonName Name { get; set; }
    public string City { get; set; }
}

public class PersonName
{
    public string First { get; set; }
    public string Last { get; set; }
}

并尝试以这种方式使用它

Person person = new Person
{
    Name = new PersonName { First = "John", Last = "Doe" },
    City = "New York City"
};

OutputField(person, m => m.City);
OutputField(person, m => m.Name.First);

属性 City 的输出有效,但是 属性 First of the 属性 Name 的输出抛出 System.Reflection.TargetException:'Object does not match target type.' 错误。如何让我的测试功能发挥作用?

CSharpie 指出的“组合 lambda 表达式以检索嵌套值”似乎在解决类似的问题,但不清楚我将如何将答案应用到我的函数中。

错误是您在访问 PersonName.First 时将 Person 传递给 prop.GetValue。该方法试图从 Person 获取 属性 First 但不能。您需要将正确的对象传递给 prop.GetValue。我做了这个通用方法来实现你想要的。一个限制是它只检查字段和属性,我稍后会尝试添加方法

public static void OutputField < T > (T item, Expression < Func < T, string >> outExpr) 
{
  // Get the expression as string
  var str = outExpr.ToString();

  // Get the variable of the expresion (m, m => ...)
  string pObj = str.Substring(0, str.IndexOf(' '));

  // Get all the members in the experesion (Name, First | m.Name.First);
  string[] members = new string(str.Skip(pObj.Length * 2 + 5).ToArray()).Split('.');

  // Last object in the tree
  object lastMember = item;
  // The type of lastMember
  Type lastType = typeof(T);

  // Loop thru each member in members
  for (int i = 0; i < members.Length; i++) 
  {
    // Get the property value
    var prop = lastType.GetProperty(members[i]);
    // Get the field value
    var field = lastType.GetField(members[i]);

    // Get the correct one and set it as last member
    if (prop is null) 
    {
      lastMember = field.GetValue(lastMember);
    }
    else 
    {
      lastMember = prop.GetValue(lastMember, null);
    }

    // Set the type of the last member
    lastType = lastMember.GetType();
  }

  // Print the value
  Console.WriteLine($ "value={lastMember}");
}

编辑: 原来你可以编译表达式并调用它

public static void OutputField<T>(T item, Expression<Func<T, string>> outExpr)
{
  Console.WriteLine($"value={outExpr.Compile().Invoke(item)}");
}

这是我根据@Andrew 对 get 的回答和我为 get 找到的一些其他代码想出的。我不确定是否有等效的更简单的方法来完成设置。

public static void OutputField<T>(T p, Expression<Func<T, string>> get)
{
    // get the value
    string v = get.Compile().Invoke(p).ToString();
    Console.WriteLine($"retrieved value={v}");

    // set a value
    var expr = (MemberExpression)get.Body;
    var param = Expression.Parameter(typeof(string), "value");
    var set = Expression.Lambda<Action<T, string>>(Expression.Assign(expr, param), get.Parameters[0], param);
    var action = set.Compile();
    action(p, "a new value");
}