Я хочу динамически применять предикаты к списку динамических объектов. Мое решение работает хорошо, когда я использую реальные объекты, но оно не работает с динамическими объектами, и я не могу понять, в чем проблема.
Примечание: я искал Stackoverflow, ни один из подобных вопросов не использует список динамических объектов.
У меня есть список динамических объектов, таких как следующий код. Список содержит два динамических объекта с двумя свойствами (Name, CreateDate). Я использовал класс JsonConvert для создания динамических объектов:
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 элемента.
Теперь я хочу отфильтровать список, чтобы получить элемент с именем Джон (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. Вот что я пробовал (см. Комментарии Не работает в следующем коде):
...
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);
}
...
Спасибо @IvanStoev. Вы были правы. Не могли бы вы помочь мне со вторым обновлением?





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 при каждом добавлении. Если вы этого не сделаете, в вашем списке не будет ничего, кроме двух «Сар».
Что ж, после небольшой работы над этим решение у меня сработало.
Я использовал 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));
}
Объект будет создан во время выполнения, и его свойства также будут определены во время выполнения. Я не знаю свойств. Предположим, у меня есть массив имен свойств.
Мне удалось воспроизвести проблему (после вашего ОБНОВЛЕНИЕ 2, который дал мне идею), изменив пример кода ExpandoObject
dictionary.Add("Name", "John");
к
dictionary.Add("Name", new string("John".ToCharArray()));
чтобы избежать постоянного интернирования string, которое приводит нас к проблеме в коде динамического выражения.
Тип динамической экспрессии - object, следовательно, Expression.Equal преобразуется в оператор объект==, то есть ReferenceEquals. Вот почему пример работает с постоянными строками, а не со строками, созданными во время выполнения.
Здесь вам нужно использовать реальный тип недвижимости. Поэтому просто приведите (Expression.Convert) результат метода доступа к динамическому свойству к ожидаемому типу:
var pname = Expression.Convert(Expression.Dynamic(name, typeof(object), pe), typeof(string));
Теперь выражения, которые относятся к выражению pname, будут разрешаться с правильным типом (в этом конкретном случае Equal будет преобразовываться в оператор перегруженной строки ==, который правильно сравнивает строки по значению. То же самое для типов значений, таких как int, DateTime и т. д.).
Когда я привязываю результат к Gridview, возникает ошибка (столбец) Имя не найдено, но в целом ответ правильный! (Поле или свойство с именем "Имя" не найдено в выбранном источнике данных)
Связывание данных (WinForms) в целом не работает с динамическими источниками (службы TypeDescriptor, в частности, PropertyDescriptor по умолчанию используют отражение), поэтому у вас будет та же проблема без фильтра. Вы можете рассмотреть возможность использования Динамический LINQ или подобной библиотеки для создания реальных типов во время выполнения и использовать стандартный Expression.Property для доступа к их свойствам.
не могли бы вы ответить на этот вопрос: stackoverflow.com/questions/65434500/…
@theGhostN Конечно, но в нынешней формулировке это неясно и требует пояснений.
Ваш код (с
ExpandoObject) у меня работает (lst2содержит единственный объект с именем "John").