我感兴趣的使用lambda表达式创建属性选择的树。
使用场景是,我们有一些代码,不会对对象图一些回归反射,而且,我们目前正在使用的属性标记的属性应遍历的递归限制的范围。 即获取对象的所有装饰性,如果该属性是引用类型与装饰性,重复每个那些太。
使用属性的限制是,你只能把它们放在你的类型控制源。 lambda表达式树允许在任意类型的公共成员范围之内。
这将是很方便有一个速记的方式来定义这些表达,它反映了对象图的结构。
最后,我很想有这样的事情:
Selector<MyType> selector = new [] {
(t => Property1),
(t => Property2)
{
p => NestedProperty1,
p => NestedProperty2
}
};
现在,我能做的最好的声明为每个节点明确这样的一个实例:
var selector = new Selector<MyType>()
{
new SelectorNode<MyType, Property1Type>(t => Property1),
new SelectorNode<MyType, Property2Type>(t => Property2)
{
new SelectorNode<Property2Type, NestedProperty1Type>(p => NestedProperty1),
new SelectorNode<Property2Type, NestedProperty2Type>(p => NestedProperty2)
},
};
有没有错码的,但你必须明确地写出每个节点的类型参数,因为编译器不能推断类型参数。 这是一种痛苦。 和丑陋。 我已经看到了一些令人难以置信的语法糖在那里,我相信一定有更好的办法。
由于我缺乏像动态,CO /逆变仿制药和表达式树“高” C#概念的理解,我想我会场上的问题在那里,看看有没有大师知道的方式来实现这一目标(或某事而不是像它?)
作为参考,这些都为声明Selector
和SelectorNode
是实现我在帖子中描述的结构类:
public interface ISelectorNode<T> {}
public class Selector<T>: List<ISelectorNode<T>>{}
public class SelectorNode<T, TOut>: List<ISelectorNode<TOut>>, ISelectorNode<T>
{
public SelectorNode(Expression<Func<T, TOut>> 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<Dummy>
{
new SelectorNode<Dummy, ChildDummy>(m => m.Child)
{
new SelectorNode<ChildDummy, string>(m => m.FakeProperty)
}
};
}
}
编辑在扩大纳瓦尔上的回答的兴趣:
利用C#的集合初始化语法,我们可以得到的代码如下所示:
var selector = new Selector<Dummy>
{
(m => m.Child),
{dummy => dummy.Child,
c => c.FakeProperty,
c => c.FakeProperty
}
};
这是如果我们的SelectorNode类的添加方法如下所示:
public class Selector<T> : List<ISelectorNode<T>>
{
public SelectorNode<T, T, TOut> Add<TOut>(Expression<Func<T, TOut>> selector, params Expression<Func<TOut, object>>[] children)
{
return SelectorNode<T, T, TOut>.Add(this, this, selector);
}
}
必须有利用这种语法的方式!
Answer 1:
我在这个阶段,我已经变得麻木思维太多选择承认,希望这是我最后一次.. :)
最后,你已经在你的问题中提到的一个- Expression<Func<T, object>>
路线。 我不知道我怎样才能更好地这又不失一些编译时的安全性。 非常相似,我的第一个答案:
public class Selector<T> : List<ISelectorNode<T>>
{
public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
{
return new SelectorNode<T, TOut>(selector);
}
public void Add<TOut>(Expression<Func<T, TOut>> selector)
{
var node = new SelectorNode<T, TOut>(selector);
Add(node);
}
}
public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
public SelectorNode(Expression<Func<T, TOut>> selector)
{
}
public ISelectorNode<T> Add(params Expression<Func<TOut, object>>[] selectors)
{
foreach (var selector in selectors)
base.Add(new SelectorNode<TOut, object>(selector));
return this;
}
public ISelectorNode<T> Add(params ISelectorNode<TOut>[] nodes)
{
AddRange(nodes);
return this;
}
}
而你拨打:
var selector = new Selector<Person>
{
Selector<Person>.Get(m => m.Address).Add
(
Selector<Address>.Get(x => x.Place),
Selector<Address>.Get(x => x.ParentName).Add
(
x => x.Id,
x => x.FirstName,
x => x.Surname
)
),
Selector<Person>.Get(m => m.Name).Add
(
x => x.Id,
x => x.FirstName,
x => x.Surname
),
m => m.Age
};
这一切是我的最爱到现在为止(如果提供)..
Answer 2:
编辑:我的下面的答案不可原谅不回答这个问题。 我有点误读了。 我将提供另一种答案,其实可以做的工作。 保持这个答案开放的,因为它可以帮助别人的未来有关的东西。
这是你可以用流畅的界面来管理,但可能不会作出削减为您服务。
让你的选择类是这样的:
public class Selector<T> : List<ISelectorNode<T>>
{
public SelectorNode<T, TOut> Add<TOut>(Expression<Func<T, TOut>> selector)
{
return SelectorNode<T, TOut>.Add(this, selector);
}
}
public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
//move this common functionality to a third static class if it warrants.
internal static SelectorNode<T, TOut> Add(List<ISelectorNode<T>> list, Expression<Func<T, TOut>> selector)
{
var node = new SelectorNode<T, TOut>(selector);
list.Add(node);
return node;
}
SelectorNode(Expression<Func<T, TOut>> selector) //unhide if you want it.
{
}
public SelectorNode<TOut, TNextOut> Add<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
{
return SelectorNode<TOut, TNextOut>.Add(this, selector);
}
}
现在,您可以拨打:
var selector = new Selector<Dummy>();
selector.Add(m => m.Child).Add(m => m.FakeProperty); //just chain the rest..
我个人觉得这不是在质疑你的方法更具有可读性,但并不像直观的或怪异:)我不认为你可以把它在同一行(可悲的是:(),但有可能是一个艰辛的道路。
更新:
一个班轮:
public class Selector<T> : List<ISelectorNode<T>>
{
public SelectorNode<T, T, TOut> Add<TOut>(Expression<Func<T, TOut>> selector)
{
return SelectorNode<T, T, TOut>.Add(this, this, selector);
}
}
public class SelectorNode<S, T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
//move this common functionality to a third static class if it warrants.
internal static SelectorNode<S, T, TOut> Add(Selector<S> parent, List<ISelectorNode<T>> list,
Expression<Func<T, TOut>> selector)
{
var node = new SelectorNode<S, T, TOut>(parent, selector);
list.Add(node);
return node;
}
Selector<S> parent;
SelectorNode(Selector<S> parent, Expression<Func<T, TOut>> selector) //unhide if you want it.
{
this.parent = parent;
}
public SelectorNode<S, TOut, TNextOut> Add<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
{
return SelectorNode<S, TOut, TNextOut>.Add(parent, this, selector);
}
public Selector<S> Finish()
{
return parent;
}
}
用法:
var selector = new Selector<Dummy>().Add(m => m.Child).Add(m => m.FakeProperty).Finish();
//or the earlier
var selector = new Selector<Dummy>();
selector.Add(m => m.Child).Add(m => m.FakeProperty); //just chain the rest, no need of Finish
第一种方法的优点:
更简单
不改变现有定义(的SelectorNode
)
第二优势:
- 提供一个清洁的呼叫。
这两种方法的缺点少可能是因为现在你有一个内部的静态方法Add
用于共享具有这两种选择课后没有意义的常用功能,但是这是宜居带我猜。 您可以删除的方法和重复的代码(或硬盘的方式,巢SelectorNode
内Selector
和隐藏实施,外界如果SelectorNode
有外没有任何意义Selector
类,或者甚至更糟,使其保护,并从其他继承一个类)
一个建议:你也许最想往的组成方式比你的继承方式List<T>
秒。 你的类名(选择)不给它下一个集合的概念。 问得好顺便说一句!
Answer 3:
你的实际执行是非常干净和可读性,可能会有点详细为你喜欢的-从集合初始糖实例化集合实例(用时仅适用的事实,问题源于new
的课程构造关键字)和可悲的是C#不推断构造类型 。 既然排除了,你现在要做的,至少在一定程度上是什么。
和语法像这样
(m => m.Child)
.SomeAddMethod(c => c.FakeProperty)
不工作没有明确说明什么拉姆达实际上代表的 ,即使你有扩展方法SomeAddMethod
上Expression<Func<T, TOut>>
。 我不得不说这是有时皮塔。
什么是可以做到的是尽量减少型规格。 最常见的方法是创建一个静态类,它会要求你只提供正式参数类型(在你的情况T
)和一次正式的参数类型是已知的返回类型( TOut
)将被推断从参数Expression<Func<T, TOut>>
。
让我们一步一步来。 考虑一个比较复杂的类层次结构:
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<T> : List<ISelectorNode<T>>
{
public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
{
return new SelectorNode<T, TOut>(selector);
}
}
public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
internal SelectorNode(Expression<Func<T, TOut>> selector)
{
}
}
现在,您可以在手动但要少得多参数输入添加所有。 事情是这样的:
var selector = new Selector<Person>();
var pA = Selector<Person>.Get(m => m.Address);
var aS = Selector<Address>.Get(m => m.Place);
var aN = Selector<Address>.Get(m => m.ParentName);
var nI1 = Selector<Name>.Get(m => m.Id);
var nS11 = Selector<Name>.Get(m => m.FirstName);
var nS12 = Selector<Name>.Get(m => m.Surname);
var pN = Selector<Person>.Get(m => m.Name);
var nI2 = Selector<Name>.Get(m => m.Id);
var nS21 = Selector<Name>.Get(m => m.FirstName);
var nS22 = Selector<Name>.Get(m => m.Surname);
var pI = Selector<Person>.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<T> : List<ISelectorNode<T>>
{
public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
{
return new SelectorNode<T, TOut>(selector);
}
public Selector<T> Add(params ISelectorNode<T>[] nodes)
{
AddRange(nodes);
return this;
}
}
public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
internal SelectorNode(Expression<Func<T, TOut>> selector)
{
}
public ISelectorNode<T> Add(params ISelectorNode<TOut>[] nodes)
{
AddRange(nodes);
return this;
}
}
现在,您可以拨打:
var selector = new Selector<Person>().Add
(
Selector<Person>.Get(m => m.Address).Add
(
Selector<Address>.Get(x => x.Place),
Selector<Address>.Get(x => x.ParentName).Add
(
Selector<Name>.Get(x => x.Id),
Selector<Name>.Get(x => x.FirstName),
Selector<Name>.Get(x => x.Surname)
)
),
Selector<Person>.Get(m => m.Name).Add
(
Selector<Name>.Get(x => x.Id),
Selector<Name>.Get(x => x.FirstName),
Selector<Name>.Get(x => x.Surname)
),
Selector<Person>.Get(m => m.Age)
);
干净多了,但我们可以用集合初始化语法,使它看起来稍微好一点。 不需Add(params)
在方法Selector<T>
你会得到:
public class Selector<T> : List<ISelectorNode<T>>
{
public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
{
return new SelectorNode<T, TOut>(selector);
}
}
var selector = new Selector<Person>
{
Selector<Person>.Get(m => m.Address).Add
(
Selector<Address>.Get(x => x.Place),
Selector<Address>.Get(x => x.ParentName).Add
(
Selector<Name>.Get(x => x.Id),
Selector<Name>.Get(x => x.FirstName),
Selector<Name>.Get(x => x.Surname)
)
),
Selector<Person>.Get(m => m.Name).Add
(
Selector<Name>.Get(x => x.Id),
Selector<Name>.Get(x => x.FirstName),
Selector<Name>.Get(x => x.Surname)
),
Selector<Person>.Get(m => m.Age)
};
通过具有其他Add
在超负荷Selector<T>
象下面这样可以最大限度地减少一些打字,但是这太疯狂了:
public class Selector<T> : List<ISelectorNode<T>>
{
public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
{
return new SelectorNode<T, TOut>(selector);
}
public void Add<TOut>(Expression<Func<T, TOut>> selector)
{
var node = new SelectorNode<T, TOut>(selector);
Add(node);
}
}
var selector = new Selector<Person>
{
Selector<Person>.Get(m => m.Address).Add
(
Selector<Address>.Get(x => x.Place),
Selector<Address>.Get(x => x.ParentName).Add
(
Selector<Name>.Get(x => x.Id),
Selector<Name>.Get(x => x.FirstName),
Selector<Name>.Get(x => x.Surname)
)
),
Selector<Person>.Get(m => m.Name).Add
(
Selector<Name>.Get(x => x.Id),
Selector<Name>.Get(x => x.FirstName),
Selector<Name>.Get(x => x.Surname)
),
m => m.Age // <- the change here
};
这工作,因为集合初始化可以调用不同的Add
过载。 但我个人更喜欢以前调用的一贯作风。
Answer 4:
一些更(集合初始化)糖烂摊子:
public class Selector<T> : List<ISelectorNode<T>>
{
public void Add(params Selector<T>[] selectors)
{
Add(this, selectors);
}
static void Add<TOut>(List<ISelectorNode<TOut>> nodes, Selector<TOut>[] selectors)
{
foreach (var selector in selectors)
nodes.AddRange(selector);
//or just, Array.ForEach(selectors, nodes.AddRange);
}
public void Add<TOut>(Expression<Func<T, TOut>> selector)
{
var node = new SelectorNode<T, TOut>(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<T> InnerAdd<TOut>(params Selector<TOut>[] selectors)
{
foreach (SelectorNode<T, TOut> node in this)
Add(node, selectors);
//or just, ForEach(node => Add((SelectorNode<T, TOut>)node, selectors));
return this;
}
}
public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
internal SelectorNode(Expression<Func<T, TOut>> selector)
{
}
}
现在这样称呼它:
var selector = new Selector<Person>
{
new Selector<Person>
{
m => m.Address
}.InnerAdd
(
new Selector<Address>
{
n => n.Place
},
new Selector<Address>
{
n => n.ParentName
}.InnerAdd
(
new Selector<Name>
{
o => o.Id,
o => o.FirstName,
o => o.Surname
}
)
),
new Selector<Person>
{
m => m.Name
}.InnerAdd
(
new Selector<Name>
{
n => n.Id,
n => n.FirstName,
n => n.Surname
}
),
m => m.Age
};
这是否帮助? 我不认为如此。 很多令人讨厌,但很少直观。 更糟的是,不存在固有的类型安全(这一切都依赖于你提供什么类型的Selector<T>
集合初始化)。
Answer 5:
还有一个 - 没有类型规范可言,但难看:)
static class Selector
{
//just a mechanism to share code. inline yourself if this is too much abstraction
internal static S Add<R, S, T, TOut>(R list, Expression<Func<T, TOut>> selector,
Func<SelectorNode<T, TOut>, S> returner) where R : List<ISelectorNode<T>>
{
var node = new SelectorNode<T, TOut>(selector);
list.Add(node);
return returner(node);
}
}
public class Selector<T> : List<ISelectorNode<T>>
{
public Selector<T> AddToConcatRest<TOut>(Expression<Func<T, TOut>> selector)
{
return Selector.Add(this, selector, node => this);
}
public SelectorNode<T, TOut> AddToAddToItsInner<TOut>(Expression<Func<T, TOut>> selector)
{
return Selector.Add(this, selector, node => node);
}
}
public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
internal SelectorNode(Expression<Func<T, TOut>> selector)
{
}
public SelectorNode<T, TOut> InnerAddToConcatRest<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
{
return AddToConcatRest(selector);
}
public SelectorNode<TOut, TNextOut> InnerAddToAddToItsInnerAgain<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
{
return AddToAddToItsInner(selector);
}
//or just 'Concat' ?
public SelectorNode<T, TOut> AddToConcatRest<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
{
return Selector.Add(this, selector, node => this);
}
public SelectorNode<TOut, TNextOut> AddToAddToItsInner<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
{
return Selector.Add(this, selector, node => node);
}
}
我已经给描述性名称的功能,使意图明确。 我不打算详细什么个别的呢,我猜的函数名就足以说明。 去通过前面的例子:
var selector = new Selector<Person>();
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<Person>();
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<Person>();
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<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
internal SelectorNode(Expression<Func<T, TOut>> selector)
{
}
public SelectorNode<T, TOut> Add<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
{
return Selector.Add(this, selector, node => this);
}
public SelectorNode<TOut, TNextOut> AddToAddToItsInner<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
{
return Selector.Add(this, selector, node => node);
}
}
现在使用:
var selector = new Selector<Person>();
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);
也许有很多可能的ESP其他替代品,当你去的各种方法的组合。 在方法链接的此特定情况下,如果该呼叫者混淆,另一种可能性是一味从呼叫者侧添加并在内部丢弃所述重复。 事情是这样的:
var selector = new Selector<Person>();
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.
在内部,你会需要打破表达降至件,建立基于他们自己的表达。 如果我有时间,我会做出一些以后阶段的答案。
Answer 6:
有一件事我要问你的是,为什么不使用反射,并保存自己从提供参数的麻烦? 您可以使用递归通过节点(属性)遍历并手动建立从那里树(见线程本或本 )。 这可能不会给你,你或许会想要的那种灵活性,虽然。
表达是不是我的强项,所以以此为伪代码。 当然,你将有更多的工作需要做。
public class Selector<T> : List<ISelectorNode<object>>
{
public Selector()
{
Add(typeof(T), this);
}
void Add(Type type, List<ISelectorNode<object>> 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<object>>, ISelectorNode<object>
{
internal SelectorNode(LambdaExpression selector)
{
}
}
与用法:
var selector = new Selector<Person>();
只是仅此而已。 这将产生你可能不希望这些属性,如性能上内置的类型,如DateTime
, string
等,但我认为它的琐碎绕过它们。 或者更好的,你可以创建自己的规则,并通过他们来确定遍历应该如何发生。
文章来源: Using nested collections of lambda expressions to create an object graph