我有兴趣利用 lambda 表达式来创建属性选择器树。
使用场景是我们有一些代码在对象图上做一些递归反射,为了限制递归的范围,我们目前正在使用 Attributes 来标记应该遍历哪些属性。即获取对象的所有修饰属性,如果该属性是具有修饰属性的引用类型,则也对每个对象重复。
使用属性的限制是您只能将它们放置在您控制其来源的类型上。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)
},
};
这段代码没有任何问题,但是您必须显式地写出每个节点的类型参数,因为编译器无法推断类型参数。这是一种痛苦。而且丑陋。我已经看到了一些令人难以置信的语法糖,并且确信一定有更好的方法。
由于我对动态、协/逆变泛型和表达式树等“高级”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)
}
};
}
}
为了扩展nawal的答案而进行了编辑:
利用 C# 的集合初始化语法,我们可以得到如下代码:
var selector = new Selector<Dummy>
{
(m => m.Child),
{dummy => dummy.Child,
c => c.FakeProperty,
c => c.FakeProperty
}
};
如果我们的 SelectorNode 类的 Add 方法如下所示:
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);
}
}
必须有一种方法可以利用这种语法!