使用lambda表达式的嵌套集合来创建对象图
本文关键字:创建 创建对象 对象图 集合 嵌套 lambda 表达式 使用 | 更新日期: 2023-09-27 18:13:42
我对利用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#概念的理解,如动态,co/逆变泛型和表达式树,我想我应该提出这个问题,看看是否有专家知道实现这一目标的方法(或类似的东西?)
作为参考,这些是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);
}
}
必须有一种方法来利用这种语法!
编辑:不可原谅的是,我下面的答案没有回答这个问题。不知怎么的,我读错了。我将提供另一个答案,它实际上可以解决这个问题。保持这个答案的开放性,因为它可能在未来的相关事情上帮助别人。
这是你可以用一个流畅的界面来处理的事情,但可能不适合你。
让你的选择器类像这样:
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>
s,您可能更希望采用组合方式,而不是继承方式。你的类名(选择器)并没有给出它下面的集合的概念。顺便说一句,好问题!
我不得不承认,在这个阶段,我已经麻木了,考虑了太多的选择,希望这是我的最后一次…:)
最后,您在问题中提到的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
};
到目前为止,这是我最喜欢的(如果可以的话)…
你的实际实现是非常干净和可读的,可能有点冗长的你喜欢-问题源于这样一个事实,集合初始化糖只在实例化集合实例时才起作用(当然在构造函数上使用new
关键字),遗憾的是c#不能从构造函数推断类型。这就排除了你想要做的事情,至少在某种程度上。
和这样的语法
(m => m.Child)
.SomeAddMethod(c => c.FakeProperty)
即使在Expression<Func<T, TOut>>
上有扩展方法SomeAddMethod
,如果不显式地声明lambda实际代表什么,也不能工作。我不得不说,这些有时是pita。
可以做的是尽量减少类型规格。最常见的方法是创建一个静态类,它将要求您只提供形式参数类型(在您的例子中是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)
);
整洁多了,但是我们可以使用集合初始化语法使它看起来更好一些。在Selector<T>
中不需要Add(params)
方法,你得到:
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)
};
通过在Selector<T>
中添加另一个Add
重载,你可以减少更多的输入,但这太疯狂了:
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
重载。但我个人更喜欢之前通话的一贯风格。
更多的(集合初始化)糖混乱:
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>
集合初始化器提供的类型)。
又一个——根本没有类型规范,但很难看:)
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);
可能有很多其他的选择,特别是当你想要各种方法的组合。在这种方法链的特殊情况下,如果这会使调用方感到困惑,另一种可能性是从调用方盲目地添加,并在内部丢弃重复项。像这样:
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.
在内部,你需要将表达式分解成小块,并基于它们构建你自己的表达式。如果我有时间,我会在稍后的某个阶段给出答案。我要问您的一件事是,为什么不使用反射来避免提供参数的麻烦?您可以使用递归遍历节点(属性)并从那里手动构建树(参见线程this或this)。但是,这可能不会给您想要的那种灵活性。
表达式不是我的强项,所以把它当作伪代码。你肯定还有更多的工作要做。
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
等,但我认为绕过它们是微不足道的。或者您可以创建自己的规则,并通过它们来确定应该如何进行遍历。