c# - 如何使用 DynamicObject 在 LINQ 表达式中强制“动态”行为
问题描述
假设我有一个D
派生自DynamicObject
. 目的是D
包装一些对象,以暴露比包装对象中最初可用的属性更多的属性。
我们还假设以下表达式编译并执行,并且变量值1
符合预期。
var age = (int)((dynamic)new D(new Person{Age = 45})).Age;
现在,如果我尝试构建一个使用D
而不是Person
直接使用的表达式:
var p = Expression.Parameter(typeof(D), "p");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, "p.Age > 45");
我很高兴ParseException
告诉我“'D'类型中不存在属性或字段'Age'”。typeof(D)
用typeof(Person)
作品代替。
如何构建一个视为的p
表达式dynamic
?我已经尝试了很多东西,最终System.Linq.Dynamic
从 NuGet 使用,但仍然没有运气。
编辑:这是使用的类。编辑:我找到了一个解决方案,DynamicExpression.CreateClass()
但我不太高兴,因为它有效地复制了我的对象,有时可能有很深的层次结构。此外,如果表达式只涉及一个属性,这将是非常低效的。
public class D : DynamicObject
{
Person _p;
public D(Person p)
{
_p = p;
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return new [] {"Age"};
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name;
if (name == "Age")
{
result = _p.Age;
return true;
}
result = null;
return false;
}
}
class Person
{
public int Age { get; set; }
}
解决方案
答案更新为原始(中风)被证明是无关紧要的。
我做了另一项研究,可能找到了解决您问题的方法。你的问题缺乏更多的背景,但我想它对你有用。
解决方案
使动态行为起作用的第一件事是IDynamicMetaObjectProvider
在类中实现接口。该接口表示动态对象,并具有GetMetaObject
返回的单一方法DynamicMetaObject
。在这种情况下,PersonMetaObject 类。
public sealed class Person : IDynamicMetaObjectProvider {
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public int GetAge() {
return DateTime.Today.Year - BirthDate.Year;
}
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) {
return new PersonMetaObject(parameter, this);
}
}
现在您必须实现对PersonMetaObject
类的覆盖以允许自定义运行时行为。(作为有趣的部分阅读)
internal class PersonMetaObject : DynamicMetaObject {
private TypeInfo _typeInfo = typeof(Person).GetTypeInfo();
public PersonMetaObject(Expression parameter, Person value)
: base(parameter, BindingRestrictions.Empty, value) { }
public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
Expression self = GetSelfExpression();
string propertyName = binder.Name;
return BindGetInternal(self, propertyName);
}
public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) {
Expression self = GetSelfExpression();
string propertyName = indexes.First().Value.ToString();
return BindGetInternal(self, propertyName);
}
private DynamicMetaObject BindGetInternal(Expression self, string propertyName) {
switch (propertyName) {
// Dynamic value computed in runtime
case "FullName": {
// Get FirstName property
Expression firstName = Expression.Property(self, nameof(Person.FirstName));
// Get LastName property
Expression lastName = Expression.Property(self, nameof(Person.LastName));
// Create constant containing space character
Expression space = Expression.Constant(" ");
Expression stringArray = Expression.NewArrayInit(typeof(string), firstName, lastName);
Expression fullName = Expression.Call(null, typeof(string).GetMethod(nameof(String.Join), new[] { typeof(string), typeof(string[]) }), space, stringArray);
// Create and return dynamic object metadata
return new DynamicMetaObject(Expression.Convert(fullName, typeof(object)), BindingRestrictions.GetTypeRestriction(Expression, LimitType));
}
// Defined method invoked
case "Age": {
// Get method info using type reflection
MethodInfo getAge = _typeInfo.GetMethod(nameof(Person.GetAge));
// Call reflected method
Expression age = Expression.Call(self, getAge);
// Create and return dynamic object metadata
return new DynamicMetaObject(Expression.Convert(age, typeof(object)), BindingRestrictions.GetTypeRestriction(Expression, LimitType));
}
//
default: {
// if none case was hit we need to make sure to return declared property if exists
if(_typeInfo.GetMembers().Any(m => m.Name == propertyName)) {
return new DynamicMetaObject(Expression.Convert(Expression.Property(self, propertyName), typeof(object)), BindingRestrictions.GetTypeRestriction(Expression, LimitType));
}
// If we get here something is wrong. To avoid exception we just return default value. But you would like to change it accordingly
// Create default object
Expression @default = Expression.Default(typeof(object));
// Create and return dynamic object metadata
return new DynamicMetaObject(@default, BindingRestrictions.GetTypeRestriction(Expression, LimitType));
}
}
}
private Expression GetSelfExpression() {
return Expression.Convert(Expression, LimitType);
}
}
最后一部分是编写一些逻辑来测试它是否有效。在下面的代码中,您可以看到有两个使用 Func 的 IEnumerable 过滤器和两个 IQueryable。IQueryable 正在使用以编程方式创建的表达式。
class Program {
static void Main(string[] args) {
// Array of concrete types assigned to dynamic enumerable
IEnumerable<dynamic> enumerable = new Person[] {
new Person { BirthDate = new DateTime(1975, 8, 14), FirstName = "Alan", LastName = "Smith" },
new Person { BirthDate = new DateTime(2006, 1, 26), FirstName = "Elisa", LastName = "Ridley" },
new Person { BirthDate = new DateTime(1993, 12, 1), FirstName = "Randy", LastName = "Knowles" },
new Person { BirthDate = new DateTime(1946, 5, 8), FirstName = "Melissa", LastName = "Fincher" }
};
IQueryable<dynamic> queryable = enumerable.AsQueryable();
// Helper method to write data to console
void Write(IEnumerable<dynamic> collection) {
foreach (var item in collection) {
Console.WriteLine($"{item.FullName} born {item.BirthDate.ToShortDateString()}");
}
Console.WriteLine();
}
// Property access on dynamic object
var youngsters = enumerable
.Where(m => m.Age < 21);
Console.WriteLine("Young people to test property acces:");
Write(youngsters);
// Index access on dynamic object
var adults = enumerable
.Where(m => m["Age"] > 20 && m["Age"] < 60);
Console.WriteLine("Adult people to test index access:");
Write(adults);
// Composed lambda expression for queryable (be aware it is not going to work with EF)
var seniors = queryable
.Where(ExpressionFactory.PropertyGreaterThanPredicate("Age", 59));
Console.WriteLine("Senior people to test property access lambda expression:");
Write(seniors);
// Composed lambda expression for queryable (be aware it is not going to work with EF)
var some = queryable
.Where(ExpressionFactory.IndexLessThanPredicate("Age", 60));
Console.WriteLine("Some people to test index access lambda expression:");
Write(some);
Console.ReadKey();
}
}
ExpressionFactory
实现返回的表达式包含负责运行时调用和实现动态行为的可能性的动态表达式。这可能是您在尝试期间遇到的问题。
public static class ExpressionFactory {
public static Expression<Func<dynamic, bool>> PropertyGreaterThanPredicate(string propertyName, int value){
var constant = Expression.Constant(value);
var parameter = Expression.Parameter(typeof(object), "m");
// Creating binder to access class member
var binder = Binder.GetMember(CSharpBinderFlags.None, propertyName, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
// Dynamic operation on the binder
var dynamic = Expression.Dynamic(binder, typeof(object), parameter);
// Converting result to same type as value to compare
var converted = Expression.Convert(dynamic, constant.Value.GetType());
var predicate = Expression.GreaterThan(converted, constant);
var lambda = Expression.Lambda<Func<dynamic, bool>>(predicate, parameter);
return lambda;
}
public static Expression<Func<dynamic, bool>> IndexLessThanPredicate(string indexName, int value) {
var constant = Expression.Constant(value);
var parameter = Expression.Parameter(typeof(object), "m");
// Creating binder to access indexer
var binder = Binder.GetIndex(CSharpBinderFlags.None, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
// Dynamic operation on the binder
var dynamic = Expression.Dynamic(binder, typeof(object), parameter, Expression.Constant(indexName));
// Converting result to same type as value to compare
var converted = Expression.Convert(dynamic, constant.Value.GetType());
var predicate = Expression.LessThan(converted, constant);
var lambda = Expression.Lambda<Func<dynamic, bool>>(predicate, parameter);
return lambda;
}
}
如果您需要完整的工作示例,您可以在GitHub 存储库中找到它。
如果还有问题,请告诉我。我会尽力提供帮助。
原始答案
如何使它工作的唯一方法是使用泛型方法而不是传递参数表达式。
namespace ConsoleApp2 {
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Dynamic;
class Program {
static void Main(string[] args) {
var dynamics = new D[] {
new D(new Person { Age = 1 }),
new D(new Person { Age = 50 }),
new D(new Person { Age = 100 })
};
var first = dynamics[0].Age;
var second = dynamics.Where(x => x.Age > 45);
var filter = System.Linq.Dynamic.DynamicExpression.ParseLambda<D, bool>("Age > 45");
var third = dynamics.Where(filter.Compile());
}
public class D : DynamicObject {
Person _p;
public D(Person p) {
_p = p;
}
public Type Type => this._p.GetType();
public override IEnumerable<string> GetDynamicMemberNames() {
return new[] { "Age" };
}
public override bool TryGetMember(GetMemberBinder binder, out object result) {
var name = binder.Name;
if (name == "Age") {
result = _p.Age;
return true;
}
result = null;
return false;
}
public int Age => _p.Age;
}
public class Person {
public int Age { get; set; }
}
}
}
希望能帮助到你!
推荐阅读
- reactjs - 在 next.js 中获取 url 查询参数时遇到问题
- c# - FFmpeg 在 20% 时暂停提取帧(但关闭时,会将所有帧转储到磁盘)
- python - 填充多边形,列表超出范围
- javascript - WebRTC - 找出 MediaStreamTrack 类型?
- css - 使用元素的伪元素的复杂行为
- python - webrtc 与 python 在网页上显示实时温度
- python-3.5 - 从 Python 2.7 更改为 3.5 不起作用
- python - Pygame窗口一直冻结
- c++ - 在 C++ 中仅使用 std::string 成员在没有循环的情况下反转字符串
- vulkan - 重新创建时交换链中的图像数量是否会改变?