开发者

Possible to mix object initializer and collection initializer?

I define an collection init开发者_如何学运维ializer with IEnumerable as instructed here: http://msdn.microsoft.com/en-us/library/bb384062.aspx

Now I'm able to create objects within my collection initializer and they are added wih my Add() method like so:

class ArrangedPanel : RectElement
{
    private List<RectElement> arrangedChildren = new List<RectElement>();
    public int Padding = 2;

    public void Add(RectElement element)
    {
        arrangedChildren.Add(element);
        //do custom stuff here
    }

    public IEnumerator GetEnumerator()
    {
        return arrangedChildren.GetEnumerator();
    }
}

// Somewhere
debugPanel.Add(new ArrangedPanel() 
{ 
    new ButtonToggle(),
    new ButtonToggle()
});

However, if I try to set a property, such as my "Padding" field, I get an error on the collection initializers.

debugPanel.Add(new ArrangedPanel() 
{ 
    Padding = 5,
    new ButtonToggle(),
    new ButtonToggle()
});

Is it possible to set both collection initializers and object initializers?


I had a similar problem. The closest one can obviously get, is to add a property to the class which allows the collection initializer access:

In ArrangedPanel:

public ArrangedPanel Container {
   get { return this; }
}

And in code:

debugPanel.Add(new ArrangedPanel() 
{ 
    Padding = 5,
    Container = {
        new ButtonToggle(),
        new ButtonToggle()
    }
});

not too bad, I guess ?

@Edit: according to the comment by @Tseng I changed the return value of the new property to return the ArrangedObject itself instead of its List<RectElement> member. This way the ArrangedPanel.Add method is called and any (potentially more complex) logic in it is reused.

@Edit2: renamed the property ('Children' -> 'Container') in the hope that the new name better reflects the new meaning.


Unfortunately it is not possible to mix object and collection initializers. The C# 3.0 specification defines an object creation expression in section 7.5.10.1 as:

    object-creation-expression:
      new   type   (   argument-listopt   )   object-or-collection-initializeropt
      new   type   object-or-collection-initializer

As you might expect, object-or-collection-initializer is either an object initializer or a collection initializer. There is no syntax available for combining then together.


Another possibility, free of order dependency and type ambiguities, though very explicit and lengthy.

public class PaddingSetter
{
    public Padding Value { get; private set; }

    public PaddingSetter()
    {
        Value = new Padding(5);
    }
}

...

public void Add(PaddingSetter setter)
{
    Padding = setter.Value;
}

...

new ArrangedPanel() 
{ 
    new PaddingSetter(5),
    new ButtonToggle(),
    new ButtonToggle()
}


Storage-location initializers are in theory supposed to create new objects with a given state, as opposed to causing objects to progress through a series of states. Code which is supposed to put something through a series of states is supposed to be written in an object's constructor.

Initializing a storage location (field, variable, etc.) with a constant simply sets its value once. Initializing a variable with a constructor or method call will cause the method to be given everything it will need before it starts, but will do nothing to the variable until the method returns. Thus it too avoids any apparent succession of states.

Property initializers and collection initializers both violate this pattern, but operate on the assumption that many classes are designed so that creating an object and setting some properties before exposing the reference to outside code will yield results indistinguishable from an object instantaneously coming into existence with those properties. Likewise, collection initializers assume that creating a collection and then calling Add with a sequence of items will yield results indistinguishable from the collection coming into existence holding the correct values.

Not all classes which expose properties will yield the expected behavior when used with property initializers, and not all classes look like collections will yield the expected behavior when used with collection initializers, but the compiler is willing to "guess" that classes which are used with property initializers will conform to the normal property pattern, and likewise for classes which are used with collection initializers.

If setting up an object would require both calling property setters and an item-Add method, however, that would imply that the object is not a typical things with property setters, nor is it a typical collection. There would be no particular reason for the language to dictate that property setters would be invoked before items are added, nor would there any particular reason to specify that they would be invoked after. One could allow the C# source code for the initializers to specify the order in which they run, but such specification would explicitly acknowledge that the sequence of operations would affect the object's state in ways not expressed within the initializer itself. Such side-effects would tend to imply that the code in question belongs within a constructor rather than a field initializer.


I don't recommend in this case, but it's possible to use multiple Add overloads.

So in ArrangedPannel include

public void Add(int padding)
{
    Padding = padding;
}

Then can defined in code like

debugPanel.Add(new ArrangedPanel() 
{ 
    5, // is this Padding?
    new ButtonToggle(),
    new ButtonToggle()
});

But I prefer @Haymo's answer because here it's not clear what '5' is set to, and multiple int properties will probably lead to crazy code like

public void Add(int intProp)
{
    var current = intPropSetCount++;
    switch(current)
    {
        case 0: Padding = intProp; return;
        case 1: SecondProp = intProp; return;
        // ...
        default: throw new Exception();
    }
}

This idea is probably best left for combining multiple collections into 1 wrapper.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜