一些 MemberBinding LINQ 表达式的例子是什么?

18

有三种可能性,但我找不到示例:

  1. System.Linq.Expressions.MemberAssignment
  2. System.Linq.Expressions.MemberListBinding
  3. System.Linq.Expressions.MemberMemberBinding

我想编写一些单元测试来验证是否能够处理它们,但是我不知道如何编写,除了第一个似乎是 new Foo { Property = "value" } 其中 Property = "value" 是类型为 MemberAssignment 的表达式之外。

请参阅此MSDN文章

1个回答

37

我在这些示例中使用的类如下:

public class Node
{
  //initialise non-null, so we can use the MemberMemberBinding
  private NodeData _data = new NodeData();
  public NodeData Data { get { return _data; } set { _data = value; } }
  //initialise with one element so you can see how a MemberListBind
  //actually adds elements to those in a list, not creating it new.
  //Note - can't set the element to 'new Node()' as we get a Stack Overflow!
  private IList<Node> _children = new List<Node>() { null };
  public IList<Node> Children 
    { get { return _children; } set { _children = value; } }
}

public class NodeData
{
  private static int _counter = 0;
  //allows us to count the number of instances being created.
  public readonly int ID = ++_counter;
  public string Name { get; set; }
}

首先,您可以通过执行以下操作让C#编译器为您生成表达式,以便更深入地了解它们的工作方式:

Expression<Func<Node>> = () => new Node();

将生成一个内联表达式,其中包含对节点类型的ConstructorInfo的调用。在Reflector中打开输出DLL以查看我指的是什么。
首先要提到的是,这三种表达式类型通常作为MemberBinding[]数组传递给Expression.New,或者嵌套在彼此中(因为成员初始化器本质上是递归的)。
接下来进入主题...
MemberAssignment
MemberAssignment表达式表示使用给定表达式的返回值设置新实例的单个成员。它是使用Expression.Bind工厂方法在代码中生成的。这是您最常见的情况,在C#代码中等效于以下内容:
new NodeData() { /* start */ Name = "hello" /* end */ };

或者

new Node() { /* start */ Data = new NodeData() /* end */ };

MemberMemberBinding

MemberMemberBinding表示成员的内联初始化,该成员已经初始化(即newed或无法为null的结构)。它是通过Expression.MemberBind创建的,不表示创建新实例。因此,它与MemberBind方法不同,它不需要ConstructorInfo,而是需要对属性Get方法(属性访问器)的引用。因此,如果尝试以这种方式初始化一个起始为null的成员,则会导致NullReferenceException。

因此,要在代码中生成此内容,请执行以下操作:

new Node() { /* start */ Data = { Name = "hello world" } /* end */};

这可能看起来有点奇怪,但实际上正在执行属性get方法以获取对已初始化成员的引用。 有了这个引用之后,依次执行内部的MemberBindings,因此上面的代码实际上并没有覆盖Data,而是执行了以下操作:

new Node().Data.Name = "hello world";

这就是为什么需要这种表达式类型的原因,因为如果你必须设置多个属性值,除非有一些特殊的表达式/语法来完成它,否则你无法在一行中完成。如果 NodeData 还有另一个字符串成员 (OtherName) 也需要同时设置,没有初始值的语法/表达式,你需要这样做:

var node = new Node();
node.Data.Name = "first";
node.Data.OtherName = "second";

这不是一个一行代码解决的问题 - 但是下面的代码可以:

var node = new Node() { Data = { Name = "first", OtherName="second" } };

Data = 位是 MemberMemberBinding。

我希望这很清楚!

MemberListBinding

Expression.ListBind 方法创建(还需要调用 Expression.ElementInit),它类似于 MemberMemberBinding(因为不会创建对象的成员),但这次,它是 ICollection/IList 的实例,其中包含了内联元素。

new Node() { /* start */ Children = { new Node(), new Node() } /* end */ };

所以,这两个表达式是特殊情况,但肯定是你可能会遇到的东西,因为它们显然非常有用。

最后,我附上一个单元测试,你可以运行它来证明我关于这些表达式的断言 - 如果你反思方法体,你会看到相关的工厂方法在我用注释块突出的点被调用:

[TestMethod]
public void TestMethod1()
{
  Expression<Func<Node>> e = 
    () => new Node() { Data = new NodeData() };

  Expression<Func<Node>> e2 = 
    () => new Node() { Data = { Name = "MemberMemberBinding" } };

  Expression<Func<Node>> e3 = 
    () => new Node() { Children = { new Node(), new Node() } };

  var f = e.Compile();
  var f2 = e2.Compile();
  var f3 = e3.Compile();

  var node = f();
  //proves that this data was created anew as part of the expression.
  Assert.AreEqual(2, node.Data.ID);
  var node2 = f2();
  //proves that the data node's name was merely initialised, and that the
  //node data itself was not created anew within the expression.
  Assert.AreEqual(3, node2.Data.ID);
  Assert.AreEqual("MemberMemberBinding", node2.Data.Name);
  var node3 = f3();
  //count is three because the two elements in the MemberListBinding
  //merely added two to the existing first null item.
  Assert.AreEqual(3, node3.Children.Count);
}

好的,我认为这应该涵盖了。

但是,在你的代码中是否应该支持它们是另一回事! ;)


1
哇...我甚至不知道在C#中最后两个是合法的。 - luksan
@luksan - 我在研究答案时也学到了很多!我可以说,我还没有利用过C#/.NET表达式的这些特性,但总有一天... - Andras Zoltan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接