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");
}
我正在尝试使用 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");
}