Ответ 1
EDIT. Это заменяет предыдущий ответ в ответ на первый комментарий.
В следующих примерах я использую следующие классы:
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; }
}
Во-первых, вы можете заставить компилятор С# генерировать выражения для изучения того, как они работают больше, выполняя следующие действия:
Expression<Func<Node>> = () => new Node();
Создает встроенное выражение, которое содержит вызов Expression.New
, передавая конструкторInfo типа Node. Откройте выходную DLL в Reflector, чтобы увидеть, что я имею в виду.
Я должен сначала упомянуть, что эти три типа выражений, о которых вы спрашиваете, обычно передаются в массиве MemberBinding [] в Expression.New или встроены друг в друга (поскольку инициализаторы элементов по своей сути являются рекурсивными).
В сюжете...
MemberAssignment
Выражение MemberAssignment представляет параметр одного члена нового экземпляра с возвращаемым значением данного выражения. Он создается в коде с использованием метода Expression.Bind
factory. Это наиболее часто встречающееся, что вы увидите, а в коде С# это эквивалентно следующему:
new NodeData() { /* start */ Name = "hello" /* end */ };
или
new Node() { /* start */ Data = new NodeData() /* end */ };
MemberMemberBinding
MemberMemberBinding представляет собой встроенную инициализацию членов члена, который уже инициализирован (то есть newed или struct, который в любом случае не может быть нулевым). Он создается с помощью Expression.MemberBind
, а не представляет собой создание нового экземпляра. Поэтому он отличается от метода MemberBind, не принимая ConstructorInfo, а ссылается на метод Get Get (свойство accessor). В результате попытка инициализировать элемент таким образом, который начинается с нуля, приведет к исключению NullReferenceException.
Итак, чтобы сгенерировать это в коде, вы делаете это:
new Node() { /* start */ Data = { Name = "hello world" } /* end */};
Это может показаться немного странным, но здесь происходит то, что метод get для Data
выполняется для получения ссылки на уже инициализированный элемент. С этим в руке внутренние элементы 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 */ };
Итак, эти последние два выражения являются любопытными краевыми случаями, но, безусловно, это то, что вы могли бы встретить, так как они явно очень полезны.
Наконец, я прилагаю unit test, который вы можете запустить, что докажет утверждения, которые я делаю об этих выражениях, - и если вы отражаете тело метода, вы увидите, что соответствующие методы factory вызывают на пункты, которые я выделяю с помощью блоков комментариев:
[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 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);
}
Там вы идете, я думаю, что это должно было его покрыть.
Если вы должны поддерживать их в своем коде, это другое дело!;)