对象初始化语法混淆。在初始化器中使用括号?

3

我正在查看文档,以了解如何使用对象初始化程序,无论是用于匿名类型还是非匿名类型。我想找出的唯一一件事情是为什么(如果有影响的话)例子中有所不同。

例子如下:对于一个猫(Cat)

class Cat
{
    public int Age { get; set; }
    public string Name { get; set; }
}

我可以这样看待第一个示例:

Cat cat = new Cat { Age = 10, Name = "Fluffy" };

这很合理。但是,你可能会发现以下内容:
List<Cat> cats = new List<Cat>
{
    new Cat(){ Name = "Sylvester", Age=8 },
    new Cat(){ Name = "Whiskers", Age=2 }
};

现在我的问题是:为什么new Cat{...}new Cat(){...}之间会有区别? 我们为什么要使用(或不使用)括号?

我希望对一种语言进行这样的“优化”时,应该花时间考虑优点和缺点之间的平衡——它可以让你省去添加 () 的麻烦,但同时也会引起混淆和不必要的麻烦,还有一个新规则需要记住,即只有在类具有无参构造函数时才能使用这种语法糖。顺便说一句,在 C++ 中它有一个目的:零初始化与不确定值。 - mireazma
2个回答

6
这只是在C#中实例化对象的两种可接受的等效语法之一,使用对象初始化(如果只是var cat = new Cat();,则不能省略括号)。
  • 因为您的类没有显式构造函数,所以提供了一个默认的无参数构造函数给Cat类。
  • new Cat {...}允许初始化Cat对象的属性作为new Cat() {...}的快捷方式来实例化,调用上述构造函数。
构造函数部分很重要。如果没有隐式默认构造函数/显式无参数构造函数,则不能省略括号,并且仍需在其中提供参数:
public class Cat {
    public string Name;
    public int Age;

    public Cat(string s) { // since I provide a constructor with parameter here, no parameterless constructor exists
        Name = s; 
    } 
}

// ...
void TestCat() 
{

    // compilation error : 'Cat'' does not contain a constructor that takes 0 arguments
    //var badCat1 = new Cat { Name = "Felix", Age = 3} ;
    //var badCat2 = new Cat() { Name = "Felix", Age = 3} ;

    // works (but no way to remove parenthesis here, since there are parameters to pass to csontructor)
    var goodCat = new Cat("Felix") { Age = 3 } ;

    Console.WriteLine($"The cat {goodCat.Name} is {goodCat.Age} years old");
}

特殊情况:(常用于集合、列表、字典等)。

如果一个类T实现了IEnumerable(即有一个公共的GetEnumerator()函数),并且实现了Add方法,则对象初始化器将使用Add方法进行枚举时的集合操作。

示例来自https://blog.mariusschulz.com/2014/06/26/fun-with-custom-c-collection-initializers

创建一个特殊的类"Points",它行为类似于具有初始化功能的"List"。

请注意,这也使用了现有的无参数构造函数!

public class Points : IEnumerable<Point3D>
{
    private readonly List<Point3D> _points;

    public Points()
    {
        _points = new List<Point3D>();
    }

    public void Add(double x, double y, double z)
    {
        _points.Add(new Point3D(x, y, z));
    }

    public IEnumerator<Point3D> GetEnumerator()
    {
        return _points.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

使用方式:

var cube = new Points
{
    { -1, -1, -1 },
    { -1, -1,  1 },
    { -1,  1, -1 },
    { -1,  1,  1 },
    {  1, -1, -1 },
    {  1, -1,  1 },
    {  1,  1, -1 },
    {  1,  1,  1 }
};

1
对于任何踩票者,请解释一下哪里有问题,以便我可以改进或删除我的答案。 - Pac0
你可能想更详细地回答底部的问题:“new Cat{...}和new Cat(){...}之间有什么区别?为什么我们应该使用(或不使用)括号?” - Manfred Radlwimmer
1
@ManfredRadlwimmer 感谢您的建议。我添加了更多细节,并尝试不要与其他已接受的答案重复。 - Pac0
1
点赞,因为提到了集合初始化器背后的实现细节。 - Camilo Terevinto

6
如果一个对象有一个“无参构造函数”,则可以省略括号。因此,以下两种写法都是有效的。
// Assuming cat has a constructor with no parameters
Cat cat = new Cat { Age = 10, Name = "Fluffy" };
Cat cat = new Cat() { Age = 10, Name = "Fluffy" };
List<T>本身具有对象初始化程序,在其中可以提供任意数量的项目,并自动将它们添加到集合中。
List<Cat> cats = new List<Cat>()
{
    new Cat(){ Name = "Sylvester", Age=8 }, // Add sylvester to the List
    new Cat(){ Name = "Whiskers", Age=2 } // And Whiskers too
};

如上所述,您也可以在此处删除括号。
List<Cat> cats = new List<Cat>
{
    new Cat { Name = "Sylvester", Age=8 }, // Add sylvester to the List
    new Cat { Name = "Whiskers", Age=2 } // And Whiskers too
};

我认为你提到的无参数构造函数(名称)非常重要,因为它用于在不同的库中指定错误消息,如EF。 - Camilo Terevinto
@Jamiec ,看起来这是我缺少的部分:* 如果一个对象有一个无参构造函数,那么您可以省略括号*。 - knee pain
@CamiloTerevinto,你能详细解释一下你刚才指出的内容吗? - knee pain
1
@bgiuga 不带参数的构造函数被称为“无参构造函数”。重要的是要知道这个名称,因为有些库在抛出错误消息时会明确引用它们。 - Camilo Terevinto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接