可以混合对象初始化器和集合初始化器吗?

时间:2011-10-04 11:20:38

标签: c# initialization ienumerable

我按照此处的说明使用IEnumerable定义了一个集合初始值设定项: http://msdn.microsoft.com/en-us/library/bb384062.aspx

现在我可以在我的集合初始值设定项中创建对象,并将它们添加到我的Add()方法中,如下所示:

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

但是,如果我尝试设置属性,例如我的“填充”字段,则会在集合初始值设定项上出错。

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

是否可以设置集合初始值设定项和对象初始值设定项?

5 个答案:

答案 0 :(得分:12)

不幸的是,无法混合对象和集合初始值设定项。 C#3.0规范将7.5.10.1节中的对象创建表达式定义为:

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

正如您所料,object-or-collection-initializer是对象初始值设定项集合初始值设定项。没有可用于组合的语法。

答案 1 :(得分:10)

我有类似的问题。最接近的一个可以显然是在类中添加一个允许集合初始化程序访问的属性:

ArrangedPanel

public ArrangedPanel Container {
   get { return this; }
}

在代码中:

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

不是太糟糕,我想?

@Edit:根据@Tseng的评论,我更改了新属性的返回值,以返回ArrangedObject本身而不是其List<RectElement>成员。这样就调用了ArrangedPanel.Add方法,并重用了其中任何(可能更复杂的)逻辑。

@ Edit2:重命名了属性('Children' - &gt;'Container'),希望新名称能更好地反映新的含义。

答案 2 :(得分:2)

另一种可能性,没有顺序依赖和类型歧义,虽然非常明确和冗长。

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

答案 3 :(得分:1)

理论上,存储位置初始化器应该创建具有给定状态的新对象,而不是使对象在一系列状态中前进。应该通过一系列状态放置某些状态的代码应该写在对象的构造函数中。

使用常量初始化存储位置(字段,变量等)只需将其值设置一次。使用构造函数或方法调用初始化变量将使该方法在启动之前获得所需的所有内容,但在该方法返回之前不会对该变量执行任何操作。因此,它也避免了任何明显的国家继承。

属性初始值设定项和集合初始值设定项都违反了这种模式,但假设设计了许多类,以便在公开对外部代码的引用之前创建对象并设置一些属性,将产生与瞬间存在的对象无法区分的结果有这些属性。同样,集合初始化程序假定创建一个集合然后使用一系列项目调用Add将产生与保持正确值的存在集合无法区分的结果。

与属性初始值设定项一起使用时,并非所有公开属性的类都会产生预期的行为,并且并非所有类看起来像集合初始值设定项时都会产生预期的行为,但编译器愿意“猜测”哪些类与属性一起使用初始值设定项将符合常规属性模式,对于与集合初始值设定项一起使用的类也是如此。

但是,如果设置一个对象既需要调用属性设置器又需要一个item-Add方法,那么这意味着该对象不是具有属性设置器的典型事物,也不是典型的集合。语言没有特别的理由要求在添加项目之前调用属性设置器,也没有任何特定的理由来指定它们将在之后被调用。可以允许初始化器的C#源代码指定它们运行的​​顺序,但是这样的规范将明确地确认操作序列将以初始化器本身内未表达的方式影响对象的状态。这种副作用往往意味着有问题的代码属于构造函数而不是字段初始化程序。

答案 4 :(得分:0)

我不建议在这种情况下,但可以使用多个Add重载。

所以在ArrangedPannel包含

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

然后可以在代码中定义

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

但我更喜欢@ Haymo的回答,因为这里不清楚'5'的设置是什么,多个int属性可能会导致疯狂的代码,如

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

这个想法最好留给将多个集合合并到一个包装器中。