动态表达式不适用于动态对象
Dynamic Expression not working on dynamic objects
我想动态地将谓词应用于动态对象列表。当我使用实际对象时,我的解决方案运行良好,但它不适用于动态对象,我无法弄清楚问题所在。
注意:我在 Whosebug 上搜索了 none 类似的问题都在使用 list of dynamic objects。
我有一个动态对象列表,如下面的代码。该列表包含两个具有两个属性 (Name
、CreateDate
) 的动态对象。我使用 JsonConvert
class 创建动态对象:
var lst = new List<dynamic>();
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("Name", "John");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<dynamic>(JsonConvert.SerializeObject(dict)));
dict.Clear();
dict.Add("Name", "sara");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<dynamic>(JsonConvert.SerializeObject(dict)));
dict.Clear();
如您所见,lst
是一个动态对象列表,其中有 2 个项目。
现在我想过滤列表以获取名称为 Jonh (p=> p.Name == "john"
)
的项目
为此,我采用了以下方法:
ParameterExpression pe = Expression.Parameter(typeof(object), "p");
CallSiteBinder name = Binder.GetMember(CSharpBinderFlags.None, "Name", typeof(object),
new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
var pname = Expression.Dynamic(name, typeof(object), pe);
var right = Expression.Constant("John");
Expression e2 = Expression.Equal(pname, right);
var qu = Expression.Lambda<Func<dynamic, bool>>(e2, pe);
var lst2 = lst.AsQueryable().Where(qu).ToList();// Count()==0 !
lst2
应包含 1 项,但它包含 0 项。但是,如果我将原始列表 (lst
) 更改为具有 Name
属性(比方说 List<Person>
)的类型,它 lst2
正确地具有 1 个项目。
更新:
即使我使用 ExpandoObject
创建动态对象,它仍然无法工作:
dynamic obj = new ExpandoObject();
var dictionary = (IDictionary<string, object>)obj;
dictionary.Add("Name", "John");
dictionary.Add("CreateDate", DateTime.Now);
更新 2:
正如评论中提到的那样,ExpandoObject
实际上有效,问题出在 SqlDataReader
上。这是我尝试过的方法(请参阅以下代码中的 Not working 注释):
...
List<dynamic> result = new List<dynamic>();
While(dr.Read()){
dynamic obj = new ExpandoObject();
var dictionary = (IDictionary<string, object>)obj;
dictionary.Add("Name","John"); // <= this works fine
// dictionary.Add("Name",dr["Name"]); // <= Not working
// dictionary.Add("Name",dr["Name"].ToItsType()); // <= Not working
// dictionary.Add("Name",dr["Name"].ToString()); // <= Not working
dictionary.Add("CreateDate", DateTime.Now);
result.Add(obj);
}
...
dynamic obj = new ExpandoObject();
dictionary.Add("Name", "John");
dictionary.Add("CreateDate", DateTime.Now);
试试上面的代码。不需要转换,ExpandoObject 应该允许添加或删除动态对象。
为什么不直接使用动态对象而不是字典。
以下代码很有魅力:
var lst = new List<dynamic>();
dynamic obj = new ExpandoObject();
obj.Name = "John";
obj.CreateDate = DateTime.Now;
lst.Add(obj);
obj = new ExpandoObject(); // re-instantiate the obj if you want to differentiate from the List itself
obj.Name = "Sara";
obj.CreateDate = DateTime.Now.AddMonths(-10);
lst.Add(obj);
foreach (var item in lst)
{
Console.WriteLine($"{item.Name} - {item.CreateDate}");
}
您甚至可以动态过滤列表
Console.WriteLine(lst.Find(i=>i.Name == "John").Name);
希望对您有所帮助。
编辑
您需要在每次添加时重新实例化您的 dynamic obj
。如果你不这样做,你的列表将只有 2 个“Sara”。
更新
好吧,在这方面做了一些工作,这个解决方案对我有用。
我用 JsonConvert.DeserializeObject<ExpandoObject>(...)
而不是 dynamic
。然后写了一个 LookUp
方法来检查元素。我认为您的代码的第一个问题是将您的序列化对象反序列化为 dynamic
而不是 ExpandoObject
。在那次修正之后,铸造字典和获得面向键值的值并不难。
这是我的代码:
var lst = new List<dynamic>();
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("Name", "John");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(dict)));
dict.Clear();
dict.Add("Name", "Sara");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(dict)));
dict.Clear();
var res = LookUp(lst, "Name", "Sara");
然后是LookUp
方法
public static object LookUp(List<dynamic> lst, string propName, object value)
{
return lst.FindAll(i =>
{
var dic = i as IDictionary<string, object>;
return dic.Keys.Any(key => dic[key].ToString().Contains(value.ToString()));
});
}
此外,如果您不想将其转换为字典,这里有另一种方法:
private static object GetProperty(dynamic target, string name)
{
var site =
CallSite<Func<CallSite, dynamic, object>>
.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, name, target.GetType(),
new[] {CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)}));
return site.Target(site, target);
}
public static object LookUpAlt(List<dynamic> lst, string propName, object value)
{
return lst.FindAll(i => GetProperty(i, propName).Equals(value));
}
我能够通过更改 ExpandoObject
示例代码
重现该问题(在你的 UPDATE 2 给了我想法之后)
dictionary.Add("Name", "John");
到
dictionary.Add("Name", new string("John".ToCharArray()));
避免常量 string
驻留,这导致我们在动态表达式代码中出现问题。
动态表达式类型是 object
,因此 Expression.Equal
解析为 object 运算符 ==
,即 ReferenceEquals
。这就是该示例使用常量字符串而不是运行时创建的字符串的原因。
你在这里需要的是使用实际的 属性 类型。因此,只需将动态 属性 访问器的结果 (Expression.Convert
) 转换为预期类型:
var pname = Expression.Convert(Expression.Dynamic(name, typeof(object), pe), typeof(string));
现在引用 pname
表达式的表达式将解析为正确的类型(在这种特殊情况下,Equal
将解析为正确比较字符串的重载字符串 ==
运算符按值。与 int
、DateTime
等值类型相同)。
我想动态地将谓词应用于动态对象列表。当我使用实际对象时,我的解决方案运行良好,但它不适用于动态对象,我无法弄清楚问题所在。
注意:我在 Whosebug 上搜索了 none 类似的问题都在使用 list of dynamic objects。
我有一个动态对象列表,如下面的代码。该列表包含两个具有两个属性 (Name
、CreateDate
) 的动态对象。我使用 JsonConvert
class 创建动态对象:
var lst = new List<dynamic>();
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("Name", "John");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<dynamic>(JsonConvert.SerializeObject(dict)));
dict.Clear();
dict.Add("Name", "sara");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<dynamic>(JsonConvert.SerializeObject(dict)));
dict.Clear();
如您所见,lst
是一个动态对象列表,其中有 2 个项目。
现在我想过滤列表以获取名称为 Jonh (p=> p.Name == "john"
)
为此,我采用了以下方法:
ParameterExpression pe = Expression.Parameter(typeof(object), "p");
CallSiteBinder name = Binder.GetMember(CSharpBinderFlags.None, "Name", typeof(object),
new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
var pname = Expression.Dynamic(name, typeof(object), pe);
var right = Expression.Constant("John");
Expression e2 = Expression.Equal(pname, right);
var qu = Expression.Lambda<Func<dynamic, bool>>(e2, pe);
var lst2 = lst.AsQueryable().Where(qu).ToList();// Count()==0 !
lst2
应包含 1 项,但它包含 0 项。但是,如果我将原始列表 (lst
) 更改为具有 Name
属性(比方说 List<Person>
)的类型,它 lst2
正确地具有 1 个项目。
更新:
即使我使用 ExpandoObject
创建动态对象,它仍然无法工作:
dynamic obj = new ExpandoObject();
var dictionary = (IDictionary<string, object>)obj;
dictionary.Add("Name", "John");
dictionary.Add("CreateDate", DateTime.Now);
更新 2:
正如评论中提到的那样,ExpandoObject
实际上有效,问题出在 SqlDataReader
上。这是我尝试过的方法(请参阅以下代码中的 Not working 注释):
...
List<dynamic> result = new List<dynamic>();
While(dr.Read()){
dynamic obj = new ExpandoObject();
var dictionary = (IDictionary<string, object>)obj;
dictionary.Add("Name","John"); // <= this works fine
// dictionary.Add("Name",dr["Name"]); // <= Not working
// dictionary.Add("Name",dr["Name"].ToItsType()); // <= Not working
// dictionary.Add("Name",dr["Name"].ToString()); // <= Not working
dictionary.Add("CreateDate", DateTime.Now);
result.Add(obj);
}
...
dynamic obj = new ExpandoObject();
dictionary.Add("Name", "John");
dictionary.Add("CreateDate", DateTime.Now);
试试上面的代码。不需要转换,ExpandoObject 应该允许添加或删除动态对象。
为什么不直接使用动态对象而不是字典。 以下代码很有魅力:
var lst = new List<dynamic>();
dynamic obj = new ExpandoObject();
obj.Name = "John";
obj.CreateDate = DateTime.Now;
lst.Add(obj);
obj = new ExpandoObject(); // re-instantiate the obj if you want to differentiate from the List itself
obj.Name = "Sara";
obj.CreateDate = DateTime.Now.AddMonths(-10);
lst.Add(obj);
foreach (var item in lst)
{
Console.WriteLine($"{item.Name} - {item.CreateDate}");
}
您甚至可以动态过滤列表
Console.WriteLine(lst.Find(i=>i.Name == "John").Name);
希望对您有所帮助。
编辑
您需要在每次添加时重新实例化您的 dynamic obj
。如果你不这样做,你的列表将只有 2 个“Sara”。
更新
好吧,在这方面做了一些工作,这个解决方案对我有用。
我用 JsonConvert.DeserializeObject<ExpandoObject>(...)
而不是 dynamic
。然后写了一个 LookUp
方法来检查元素。我认为您的代码的第一个问题是将您的序列化对象反序列化为 dynamic
而不是 ExpandoObject
。在那次修正之后,铸造字典和获得面向键值的值并不难。
这是我的代码:
var lst = new List<dynamic>();
Dictionary<string, object> dict = new Dictionary<string, object>();
dict.Add("Name", "John");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(dict)));
dict.Clear();
dict.Add("Name", "Sara");
dict.Add("CreateDate", DateTime.Now);
lst.Add(JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(dict)));
dict.Clear();
var res = LookUp(lst, "Name", "Sara");
然后是LookUp
方法
public static object LookUp(List<dynamic> lst, string propName, object value)
{
return lst.FindAll(i =>
{
var dic = i as IDictionary<string, object>;
return dic.Keys.Any(key => dic[key].ToString().Contains(value.ToString()));
});
}
此外,如果您不想将其转换为字典,这里有另一种方法:
private static object GetProperty(dynamic target, string name)
{
var site =
CallSite<Func<CallSite, dynamic, object>>
.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, name, target.GetType(),
new[] {CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)}));
return site.Target(site, target);
}
public static object LookUpAlt(List<dynamic> lst, string propName, object value)
{
return lst.FindAll(i => GetProperty(i, propName).Equals(value));
}
我能够通过更改 ExpandoObject
示例代码
dictionary.Add("Name", "John");
到
dictionary.Add("Name", new string("John".ToCharArray()));
避免常量 string
驻留,这导致我们在动态表达式代码中出现问题。
动态表达式类型是 object
,因此 Expression.Equal
解析为 object 运算符 ==
,即 ReferenceEquals
。这就是该示例使用常量字符串而不是运行时创建的字符串的原因。
你在这里需要的是使用实际的 属性 类型。因此,只需将动态 属性 访问器的结果 (Expression.Convert
) 转换为预期类型:
var pname = Expression.Convert(Expression.Dynamic(name, typeof(object), pe), typeof(string));
现在引用 pname
表达式的表达式将解析为正确的类型(在这种特殊情况下,Equal
将解析为正确比较字符串的重载字符串 ==
运算符按值。与 int
、DateTime
等值类型相同)。