views:

200

answers:

1

There are three possibilities, but I can't find examples:

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

I want to write some unit tests to see if I can handle them, but I don't know how to write them except for the first one, which seems to be new Foo { Property = "value" } where Property = "value" is an expression of type MemberAssignment.

See also this MSDN article.

+3  A: 

EDIT This replaces the previous answer in response to the first comment.

The classes I'm using in these examples are as follows:

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; }
}

Firstly, you can get the C# compiler to generate expressions for you to investigate how they work more by doing the following:

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

Will generate an inline expression that contains a call to Expression.New, passing the ConstructorInfo of the Node type. Open the output DLL in Reflector to see what I mean.

I should first mention that these three expression types you ask about are typically passed in a MemberBinding[] array in an Expression.New, or embedded within each other (since Member initializers are inherently recursive).

On to the plot...

MemberAssignment

The MemberAssignment expression represents the setting of a single member of a new instance with the return value of a given expression. It is produced in code using the Expression.Bind factory method. This is the most common that you'll see, and in C# code this is equivalent to the following:

new NodeData() { /* start */ Name = "hello" /* end */ };

or

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

MemberMemberBinding

The MemberMemberBinding represents the inline initialisation of the members of a member that is already initialised (i.e. newed, or a struct that can't be null anyway). It is created through the Expression.MemberBind and does not represent creating a new instance. Therefore, it differs from the MemberBind method by not taking a ConstructorInfo, but a reference to a Property Get method (property accessor). As a result, an attempt to initialise a member in this way that starts off null will result in a NullReferenceException.

So, to generate this in code you do this:

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

This might seem a bit odd, but what's happening here is that the property get method for Data is being executed to obtain a reference to the already initialised member. With that in hand, the inner MemberBindings are then executed in turn, so effectively the above code is not overwriting Data, but doing this:

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

And this is why this expression type is required, because if you've got to set multiple property values, you can't do it in a one-liner, unless there's some special expression/syntax to do it. If NodeData had another string member (OtherName) that you also wanted to set at the same time, without initialiser syntax/expressions, you'd have to do this:

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

Which isn't a one liner - but this is:

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

Where the Data = bit is the MemberMemberBinding.

I hope that's clear!

MemberListBinding

Created by the Expression.ListBind method (requiring also calls to Expression.ElementInit), this is similar to the MemberMemberBinding (in that an object's member is not being created anew), but this time, it's an instance of ICollection/IList that is being added to with inline elements.:

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

So, these last two expressions are kinda edge-cases, but certainly are things that you could well come across, as they are clearly very useful.

Finally, I enclose a unit test that you can run that will prove the assertions I make about these expressions - and if you reflect the method body, you'll see that the relevant factory methods are being called at the points I highlight with the comment blocks:

[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);
}

There you go, I think that should cover it.

Whether you should be supporting them in your code is another matter! ;)

Andras Zoltan
Thanks, but both of your examples turn out to be normal MemberAssignment where the BindingType is MemberBindingType.Assignment
michielvoo
@michielvoo - okay, my examples weren't exactly correct, however I have the answer for you and am now updating my answer to reflect this. For the record, I have used reflector to figure this out.
Andras Zoltan