使用嵌套的lambda表达式集合来创建对象图
我有兴趣利用lambda表达式来创建属性选择器树。
使用场景是我们有一些代码对对象图进行一些递归reflection,并且为了限制递归的范围,我们当前正在使用Attributes来标记应该遍历哪些属性。 ie获取对象的所有修饰属性,如果该属性是具有修饰属性的引用类型,则对每个属性重复。
使用属性的限制是您只能将它们放在您控制源的类型上。 lambda表达式树允许在任意类型的公共成员上定义范围。
使用速记方式定义这些表达式会很方便,这反映了对象图的结构。
最终,我喜欢这样的事情:
Selector selector = new [] { (t => Property1), (t => Property2) { p => NestedProperty1, p => NestedProperty2 } };
现在,我能做的最好的事情就是为每个节点声明一个实例,如下所示:
var selector = new Selector() { new SelectorNode(t => Property1), new SelectorNode(t => Property2) { new SelectorNode(p => NestedProperty1), new SelectorNode(p => NestedProperty2) }, };
这段代码没有任何问题,但你必须明确地为每个节点写出类型参数,因为编译器无法推断出类型参数。 这是一种痛苦。 而且丑陋。 我在那里看到了一些令人难以置信的语法糖,我相信一定有更好的方法。
由于我对“更高”的C#概念缺乏了解,比如动态,共同/逆变的generics和表达树,我想我会在那里提出问题并看看是否有任何大师知道如何实现这一点(或者更像是它?)
作为参考,这些是Selector
和SelectorNode
类的声明,它们实现了我在post中描述的结构:
public interface ISelectorNode {} public class Selector: List<ISelectorNode>{} public class SelectorNode: List<ISelectorNode>, ISelectorNode { public SelectorNode(Expression<Func> select) {} } //Examples of Usage below public class Dummy { public ChildDummy Child { get; set; } } public class ChildDummy { public string FakeProperty { get; set; } } public class Usage { public Usage() { var selector = new Selector { new SelectorNode(m => m.Child) { new SelectorNode(m => m.FakeProperty) } }; } }
编辑是为了扩大纳瓦尔的答案:
利用C#的集合初始化器语法,我们可以得到如下代码:
var selector = new Selector { (m => m.Child), {dummy => dummy.Child, c => c.FakeProperty, c => c.FakeProperty } };
这是我们的SelectorNode类的Add方法如下所示:
public class Selector : List<ISelectorNode> { public SelectorNode Add(Expression<Func> selector, params Expression<Func>[] children) { return SelectorNode.Add(this, this, selector); } }
必须有一种方法来利用这种语法!
我不得不承认,在这个阶段我已经麻木了太多的选择,希望这是我的最后一个.. 🙂
最后,您在问题中提到的那个 – Expression
route。 我不知道如何在不失去编译时安全性的情况下更好地完成这项工作。 与我的第一个答案非常相似:
public class Selector : List> { public static SelectorNode Get(Expression> selector) { return new SelectorNode(selector); } public void Add (Expression> selector) { var node = new SelectorNode(selector); Add(node); } } public class SelectorNode : List>, ISelectorNode { public SelectorNode(Expression> selector) { } public ISelectorNode Add(params Expression>[] selectors) { foreach (var selector in selectors) base.Add(new SelectorNode(selector)); return this; } public ISelectorNode Add(params ISelectorNode[] nodes) { AddRange(nodes); return this; } }
你打电话给:
var selector = new Selector { Selector .Get(m => m.Address).Add ( Selector.Get(x => x.Place), Selector.Get(x => x.ParentName).Add ( x => x.Id, x => x.FirstName, x => x.Surname ) ), Selector.Get(m => m.Name).Add ( x => x.Id, x => x.FirstName, x => x.Surname ), m => m.Age };
所有这些都是我的恩惠,直到现在(如果这是服务)..
编辑:我的下面的答案不可原谅地没有回答这个问题。 我以某种方式误读了它。 我将提供另一个可以实际完成工作的答案。 保持这个答案是开放的,因为它可能有助于将来某些相关的事情。
这是您可以使用流畅的界面管理的内容,但可能无法为您完成。
你的选择器类是这样的:
public class Selector : List> { public SelectorNode Add(Expression> selector) { return SelectorNode.Add(this, selector); } } public class SelectorNode : List>, ISelectorNode { //move this common functionality to a third static class if it warrants. internal static SelectorNode Add(List> list, Expression> selector) { var node = new SelectorNode(selector); list.Add(node); return node; } SelectorNode(Expression> selector) //unhide if you want it. { } public SelectorNode Add(Expression> selector) { return SelectorNode.Add(this, selector); } }
现在你可以打电话:
var selector = new Selector(); selector.Add(m => m.Child).Add(m => m.FakeProperty); //just chain the rest..
我个人认为这比你在问题中的方法更具可读性,但不是那么直观或令人讨厌:)我认为你不能把它放在一行(遗憾的是:(),但可能有一个难的方法。
更新:
单行:
public class Selector : List> { public SelectorNode Add(Expression> selector) { return SelectorNode.Add(this, this, selector); } } public class SelectorNode : List>, ISelectorNode { //move this common functionality to a third static class if it warrants. internal static SelectorNode Add(Selector parent, List> list, Expression> selector) { var node = new SelectorNode(parent, selector); list.Add(node); return node; } Selector parent; SelectorNode(Selector parent, Expression> selector) //unhide if you want it. { this.parent = parent; } public SelectorNode Add(Expression> selector) { return SelectorNode.Add(parent, this, selector); } public Selector Finish() { return parent; } }
用法:
var selector = new Selector().Add(m => m.Child).Add(m => m.FakeProperty).Finish(); //or the earlier var selector = new Selector (); selector.Add(m => m.Child).Add(m => m.FakeProperty); //just chain the rest, no need of Finish
第一种方法的优点:
-
更简单
-
不改变现有的定义(
SelectorNode
)
第二个优势:
- 提供更清洁的电话。
这两种方法的一个小缺点可能是,现在你有一个内部静态方法Add
用于共享在这两个选择器类之外没有任何意义的常用function,但我认为这是宜居的。 你可以删除方法和重复代码(或者硬盘方式,在SelectorNode
内部嵌套SelectorNode
,如果SelectorNode
在Selector
类之外没有任何意义,则将实现隐藏到外部世界。或者更糟糕的是让它受保护并从另一个类inheritance一个类)
一个建议:你很可能想要使用List
进行组合方式而不是inheritance方式。 您的类名(选择器)不会在其下面提供集合。 好问题顺便说一下!
你的实际实现是非常干净和可读的,可能有点你喜欢的冗长 – 问题源于这样一个事实,即集合初始化程序只在实例化集合实例时使用(当然在构造函数上使用new
关键字),遗憾的是C#没有从构造函数推断类型 。 现在排除你想要做的事情,至少在某种程度上。
像这样的语法
(m => m.Child) .SomeAddMethod(c => c.FakeProperty)
如果没有明确说明lambda实际代表什么,即使你在Expression
上有扩展方法SomeAddMethod
,也无法工作。 我不得不说这些有时候是皮塔饼。
可以做的是最小化类型规范。 最常见的方法是创建一个静态类, 它只需要提供forms参数类型(在你的情况下是T
), 一旦forms参数类型已知,返回类型( TOut
)将从参数Expression
推断出来。 Expression
。
让我们一步一步来。 考虑更复杂的类层次结构:
public class Person { public Address Address { get; set; } public Name Name { get; set; } public int Age { get; set; } } public class Address { public string Place { get; set; } public Name ParentName { get; set; } } public class Name { public int Id { get; set; } public string FirstName { get; set; } public string Surname { get; set; } }
假设你有这个(最简单的):
public class Selector : List> { public static SelectorNode Get(Expression> selector) { return new SelectorNode(selector); } } public class SelectorNode : List>, ISelectorNode { internal SelectorNode(Expression> selector) { } }
现在,您可以手动添加所有这些,但参数输入更少。 像这样的东西:
var selector = new Selector(); var pA = Selector .Get(m => m.Address); var aS = Selector.Get(m => m.Place); var aN = Selector.Get(m => m.ParentName); var nI1 = Selector.Get(m => m.Id); var nS11 = Selector .Get(m => m.FirstName); var nS12 = Selector .Get(m => m.Surname); var pN = Selector.Get(m => m.Name); var nI2 = Selector.Get(m => m.Id); var nS21 = Selector .Get(m => m.FirstName); var nS22 = Selector .Get(m => m.Surname); var pI = Selector.Get(m => m.Age); selector.Add(pA); pA.Add(aS); pA.Add(aN); aN.Add(nI1); aN.Add(nS11); aN.Add(nS12); selector.Add(pN); pN.Add(nI2); pN.Add(nS21); pN.Add(nS22); selector.Add(pI);
非常简单,但不是那么直观(我会更喜欢你的原始语法)。 也许我们可以缩短这个:
public class Selector : List> { public static SelectorNode Get(Expression> selector) { return new SelectorNode(selector); } public Selector Add(params ISelectorNode [] nodes) { AddRange(nodes); return this; } } public class SelectorNode : List>, ISelectorNode { internal SelectorNode(Expression> selector) { } public ISelectorNode Add(params ISelectorNode[] nodes) { AddRange(nodes); return this; } }
现在你可以打电话:
var selector = new Selector().Add ( Selector .Get(m => m.Address).Add ( Selector.Get(x => x.Place), Selector.Get(x => x.ParentName).Add ( Selector.Get(x => x.Id), Selector .Get(x => x.FirstName), Selector .Get(x => x.Surname) ) ), Selector.Get(m => m.Name).Add ( Selector.Get(x => x.Id), Selector .Get(x => x.FirstName), Selector .Get(x => x.Surname) ), Selector.Get(m => m.Age) );
更清洁,但我们可以使用集合初始化程序语法使其看起来稍微好一些。 在Selector
不需要Add(params)
方法,你得到:
public class Selector : List> { public static SelectorNode Get(Expression> selector) { return new SelectorNode(selector); } } var selector = new Selector { Selector .Get(m => m.Address).Add ( Selector.Get(x => x.Place), Selector.Get(x => x.ParentName).Add ( Selector.Get(x => x.Id), Selector .Get(x => x.FirstName), Selector .Get(x => x.Surname) ) ), Selector.Get(m => m.Name).Add ( Selector.Get(x => x.Id), Selector .Get(x => x.FirstName), Selector .Get(x => x.Surname) ), Selector.Get(m => m.Age) };
通过在Selector
Add
另一个Add
重载,如下所示,您可以最大限度地减少一些输入,但这很疯狂:
public class Selector : List> { public static SelectorNode Get(Expression> selector) { return new SelectorNode(selector); } public void Add (Expression> selector) { var node = new SelectorNode(selector); Add(node); } } var selector = new Selector { Selector .Get(m => m.Address).Add ( Selector.Get(x => x.Place), Selector.Get(x => x.ParentName).Add ( Selector.Get(x => x.Id), Selector .Get(x => x.FirstName), Selector .Get(x => x.Surname) ) ), Selector.Get(m => m.Name).Add ( Selector.Get(x => x.Id), Selector .Get(x => x.FirstName), Selector .Get(x => x.Surname) ), m => m.Age // <- the change here };
这是因为集合初始值设定项可以调用不同的Add
重载。 但我个人更喜欢以前呼叫的一贯风格。
更多(收集初始化)糖混乱:
public class Selector : List> { public void Add(params Selector [] selectors) { Add(this, selectors); } static void Add(List> nodes, Selector [] selectors) { foreach (var selector in selectors) nodes.AddRange(selector); //or just, Array.ForEach(selectors, nodes.AddRange); } public void Add (Expression> selector) { var node = new SelectorNode(selector); Add(node); } //better to have a different name than 'Add' in cases of T == TOut collision - when classes //have properties of its own type, eg Type.BaseType public Selector InnerAdd(params Selector [] selectors) { foreach (SelectorNode node in this) Add(node, selectors); //or just, ForEach(node => Add((SelectorNode)node, selectors)); return this; } } public class SelectorNode : List>, ISelectorNode { internal SelectorNode(Expression> selector) { } }
现在称它为:
var selector = new Selector { new Selector { m => m.Address }.InnerAdd ( new Selector { n => n.Place }, new Selector { n => n.ParentName }.InnerAdd ( new Selector { o => o.Id, o => o.FirstName, o => o.Surname } ) ), new Selector { m => m.Name }.InnerAdd ( new Selector { n => n.Id, n => n.FirstName, n => n.Surname } ), m => m.Age };
这有帮助吗? 我不这么认为。 很讨厌,但很少直观。 更糟糕的是,没有固有的类型安全性(这完全取决于您为Selector
集合初始化程序提供的类型)。
还有一个 – 根本没有类型规格,但简单丑陋:)
static class Selector { //just a mechanism to share code. inline yourself if this is too much abstraction internal static S Add(R list, Expression> selector, Func, S> returner) where R : List> { var node = new SelectorNode(selector); list.Add(node); return returner(node); } } public class Selector : List> { public Selector AddToConcatRest(Expression> selector) { return Selector.Add(this, selector, node => this); } public SelectorNode AddToAddToItsInner (Expression> selector) { return Selector.Add(this, selector, node => node); } } public class SelectorNode : List>, ISelectorNode { internal SelectorNode(Expression> selector) { } public SelectorNode InnerAddToConcatRest(Expression> selector) { return AddToConcatRest(selector); } public SelectorNode InnerAddToAddToItsInnerAgain (Expression> selector) { return AddToAddToItsInner(selector); } //or just 'Concat' ? public SelectorNode AddToConcatRest (Expression> selector) { return Selector.Add(this, selector, node => this); } public SelectorNode AddToAddToItsInner (Expression> selector) { return Selector.Add(this, selector, node => node); } }
我已经为函数提供了描述性名称以使意图清晰。 我不打算详细解释那些单独的内容,我想function名称就足够了。 按前面的例子说:
var selector = new Selector(); var pA = selector.AddToAddToItsInner(m => m.Address); var aN = pA.InnerAddToConcatRest(m => m.Place); var aS = aN.AddToAddToItsInner(m => m.ParentName); var nI1 = aS.InnerAddToConcatRest(m => m.Id); var nS11 = nI1.AddToConcatRest(m => m.FirstName); var nS12 = nS11.AddToConcatRest(m => m.Surname); var pN = selector.AddToAddToItsInner(m => m.Name); var nI2 = pN.InnerAddToConcatRest(m => m.Id); var nS21 = nI2.AddToConcatRest(m => m.FirstName); var nS22 = nS21.AddToConcatRest(m => m.Surname); var pI = selector.AddToConcatRest(m => m.Age);
或者提供替代方案(将想法推回家):
var selector = new Selector(); var pA = selector.AddToAddToItsInner(m => m.Address); var aS = pA.InnerAddToConcatRest(m => m.Place); var aN = pA.InnerAddToAddToItsInnerAgain(m => m.ParentName); var nI1 = aN.InnerAddToConcatRest(m => m.Id); var nS11 = nI1.AddToConcatRest(m => m.FirstName); var nS12 = nS11.AddToConcatRest(m => m.Surname); var pN = selector.AddToAddToItsInner(m => m.Name); var nI2 = pN.InnerAddToConcatRest(m => m.Id); var nS21 = nI2.AddToConcatRest(m => m.FirstName); var nS22 = nS21.AddToConcatRest(m => m.Surname); var pI = selector.AddToConcatRest(m => m.Age);
现在我们可以结合起来使其简洁并省略冗余变量:
var selector = new Selector(); selector.AddToConcatRest(m => m.Age).AddToAddToItsInner(m => m.Address) .InnerAddToConcatRest(m => m.Place).AddToAddToItsInner(m => m.ParentName) .InnerAddToConcatRest(m => m.Id).AddToConcatRest(m => m.FirstName).AddToConcatRest(m => m.Surname); selector.AddToAddToItsInner(m => m.Name) .InnerAddToConcatRest(m => m.Id).AddToConcatRest(m => m.FirstName).AddToConcatRest(m => m.Surname);
现在您可能已经注意到,许多Add
函数在内部执行相同的工作。 我将这些方法分开了,因为从调用方那里他们有不同的语义来执行。 如果您可以知道它在做什么/意味着什么,那么代码可以再次缩短。 将SelectorNode<,>
类更改为:
public class SelectorNode : List>, ISelectorNode { internal SelectorNode(Expression> selector) { } public SelectorNode Add(Expression> selector) { return Selector.Add(this, selector, node => this); } public SelectorNode AddToAddToItsInner (Expression> selector) { return Selector.Add(this, selector, node => node); } }
现在用法:
var selector = new Selector(); selector.AddToConcatRest(m => m.Age).AddToAddToItsInner(m => m.Address) .Add(m => m.Place).AddToAddToItsInner(m => m.ParentName) .Add(m => m.Id).Add(m => m.FirstName).Add(m => m.Surname); selector.AddToAddToItsInner(m => m.Name) .Add(m => m.Id).Add(m => m.FirstName).Add(m => m.Surname);
当你采用各种方法的组合时,可能还有很多其他的选择。 在方法链接的这种特殊情况下,如果这使调用者感到困惑,则另一种可能性是盲目地从调用方添加并在内部丢弃重复项。 像这样的东西:
var selector = new Selector(); selector.Add(m => m.Address).Add(m => m.Place); selector.Add(m => m.Address).Add(m => m.ParentName).Add(m => m.Id); //at this stage discard duplicates selector.Add(m => m.Address).Add(m => m.ParentName).Add(m => m.FirstName); //and so on selector.Add(m => m.Name)... etc selector.Add(m => m.Age);
为此,您必须为节点类引入自己的相等比较器,这使得它非常脆弱。
另一种直观的方法是直接在表达式中链接属性。 喜欢:
selector.Add(m => m.Address.Place); selector.Add(m => m.Address.ParentName.Id); selector.Add(m => m.Address.ParentName.FirstName); // and so on.
在内部,您需要将表达式分解为碎片并基于它们构建自己的表达式。 如果我有时间,我会在稍后阶段做出答案。
我要问你的一件事是为什么不使用reflection并避免提供参数的麻烦? 您可以使用递归遍历节点(属性)并从那里手动构建树(请参阅此处或此处的线程)。 但是,这可能无法为您提供您想要的灵活性。
表达不是我的强项,所以把它作为伪代码。 你肯定会有更多的工作要做。
public class Selector : List> { public Selector() { Add(typeof(T), this); } void Add(Type type, List> nodes) { foreach (var property in type.GetProperties()) //with whatever flags { //the second argument is a cool param name I have given, discard-able var paramExpr = Expression.Parameter(type, type.Name[0].ToString().ToLower()); var propExpr = Expression.Property(paramExpr, property); var innerNode = new SelectorNode(Expression.Lambda(propExpr, paramExpr)); nodes.Add(innerNode); Add(property.PropertyType, innerNode); } } } public class SelectorNode : List>, ISelectorNode
用法:
var selector = new Selector();
就是这样。 这会产生你可能不想要的那些属性,比如DateTime
, string
等内置类型的属性,但我认为绕过它们是微不足道的。 或者更好的是,您可以创建自己的规则并传递它们以确定遍历应该如何发生。