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

时间:2010-05-26 22:57:42

标签: c# .net linq expression-trees

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

  1. System.Linq.Expressions.MemberAssignment
  2. System.Linq.Expressions.MemberListBinding
  3. System.Linq.Expressions.MemberMemberBinding
  4. 我想写一些单元测试,看看我是否可以处理它们,但我不知道如何编写它们除了第一个,它似乎是new Foo { Property = "value" }其中 Property =“值“MemberAssignment类型的表达式。

    另见MSDN article

1 个答案:

答案 0 :(得分:28)

编辑这将取代之前的答案,以回应第一条评论。

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

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();

将生成包含对Expression.New的调用的内联表达式,并传递Node类型的ConstructorInfo。在Reflector中打开输出DLL以查看我的意思。

我首先要提到的是,您询问的这三种表达式类型通常在Expression.New中的MemberBinding []数组中传递,或者相互嵌入(因为成员初始化器本身就是递归的)。

关于情节...

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,而是引用Property Get方法(属性访问器)。因此,尝试以这种方式初始化成员以null开始将导致NullReferenceException。

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

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

这可能看起来有点奇怪,但这里发生的是正在执行Data的属性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);
}

你去了,我认为应该覆盖它。

您是否应该在代码中支持它们是另一回事! ;)