【问题标题】:What is the preferred way of constructing objects in C#? Constructor parameters or properties?在 C# 中构造对象的首选方法是什么?构造函数参数或属性?
【发布时间】:2010-10-26 04:05:37
【问题描述】:

我想知道,在 C# 中构造新对象的首选方法是什么?

学习一个 Person 类:

public class Person 
{
    private string name;
    private int age;

    //Omitted..
}

我应该创建它以使用:

New Person("name", 24);

New Person() { Name = "name", Age = 24 };

这只是口味问题,还是有充分的理由使用其中一种?

我可以想象一个应该只使用构造函数中的必填字段和可选字段,而不是作为构造函数参数,而是使用属性。

我说的对吗?

【问题讨论】:

标签: c# oop properties constructor


【解决方案1】:

第二种方式只是手动设置属性的语法糖:

Person p = new Person();
p.Name = "name";
p.Age = 24;

你也不依赖于构造函数,它可能不会初始化你想要设置的所有属性。

如果你的类有一个需要这两个参数的构造函数,你就不用明确地调用那个构造函数。

【讨论】:

    【解决方案2】:

    这真的取决于场景。属性方法有很多便利,因为您不必在构造函数中复制赋值。此外,大多数数据绑定场景都希望能够创建新对象,他们通常使用无参数构造函数,这是一个很大的优势。

    但是,对于不可变类型,构造方法是唯一明智的选择。有趣的是(也许)C# 4.0 中的命名/可选参数允许类似于不可变类型的对象初始化器 - see here

    构造函数方法在控制反转框架中也很流行,因为它清楚地宣传了该类需要什么才能运行。

    您可能需要混合搭配,但通常属性样式比构造函数样式更多。

    【讨论】:

      【解决方案3】:

      在构造函数中设置值会使这些属性成为强制性的,因此这意味着您无法在不设置这些属性的情况下创建新实例。 在某些情况下这是可取的,在其他情况下则不可取。

      【讨论】:

      • 这是真的,虽然对于结构你当然不能强制设置。
      • 确实如此,但那是因为结构是一种值类型,并且总是有一个(默认)值,即使它没有被初始化。
      【解决方案4】:

      我对这个问题的主要考虑是 1) 实例化对象时实例化实体将拥有多少数据以及 2) 类需要如何封装?如果一旦设置,属性就不能改变(至少,由于任何外部实体),那么我会使用构造函数,因为构造函数可以设置任何只读属性以及任何读/写属性。

      还请记住,就像任何其他方法一样,您的构造函数可以重载,因此您可以设置任意数量的方法来初始化这些属性。

      一般来说,我使用构造函数,但有时我会手动设置属性。为正确的问题使用正确的工具。

      【讨论】:

        【解决方案5】:

        我的工作始终基于您应该将值传递给构造函数,这些值对于该对象以有效状态存在是必需的。

        在您的示例中,您可以说没有年龄就不能存在新人,因此应该将其传递给构造函数。

        如果您的工作基础是一个对象应该为一个真实的工作实体建模,那么这将确定使任何对象有效所需的最小值 - 无论您将它们设置为默认值还是通过构造函数传入值。

        【讨论】:

        • +1:对象在构造后和每次方法调用后都应始终处于有效状态,因此方法不必在运行前验证自己对象的状态,只需验证其参数即可。
        【解决方案6】:

        在我看来,你应该首先决定是什么让一个人成为一个人。要正确实例化人员对象的人员属性是什么。一个人的最低要求是什么。

        应该总是有一个具有最低要求的构造函数。

        我只会将对象初始化器用于不需要实例化任何属性的类型。因此默认构造函数。

        我知道对象初始化器非常方便。其他机制也可能需要对象中的空构造函数。

        但我不认为你可以创建一个没有名字的人。

        【讨论】:

          【解决方案7】:

          一些想法:

          1. 您需要公共属性才能使用对象初始化器。所以如果有一些你不想暴露的东西,你必须通过构造函数参数来初始化它们。

          2. 如果你检查 IL,你会发现 Object Initializer 不是“原子的”。如果你写这样的代码(不是我推荐的,只是一个例子):

            using (p = New Person() {Name = GetName(), Age = GetAge()})
            {
              //blah, blah
            }
            

            如果GetAge() 出现异常,您将创建一个处于损坏状态的Person 实例。更糟糕的是,您永远无法进入 using 范围,并且该实例不会像您想象的那样被释放。

          【讨论】:

          • 我喜欢你指出它不是原子的。当你看到它时很明显,我认为我不会像你所说的那样编写代码,但如果我正在阅读别人的代码,我认为我不会接受它。
          【解决方案8】:

          首选方式取决于您的设计。

          构造函数属性用于正确构造对象所需的项。也就是说,对象为了被初始化而应该具有的任何属性都需要在构造函数中(在调用构造函数之后,您通常不想要部分初始化的对象,除非您正在创建工厂或构建器模式并且构造函数对除了工厂/构建器之外的所有人都隐藏)。

          属性初始化器最适合在您的特定用例所需的构造函数之后进行额外配置,但对于被视为已初始化的对象不是必需的。

          例如,您可以有一个代表一个人的对象。一个人需要一个名字和一个年龄来初始化,但他们居住的地址是一个可选配置。所以,name 和 age 是构造函数参数,address 是读写属性。

          Person johnDoe = new Person("John Doe", 24) { Address = "42 Adams Street" };
          

          【讨论】:

          • 这是一个很好的理由,有助于辨别何时应用什么。谢谢!
          • 我建议您也阅读 Marc Gravell 的回答 (stackoverflow.com/a/863064/23234),它提供了一些替代场景,这些场景着眼于实际用途,由于其他限制,我提出的更理想的观点并不相关。
          【解决方案9】:

          对于手动设置属性,它们必须被声明为公共的,并且您可能希望类成员是私有的。在这种情况下,构造函数是要走的路,或者编写方法来获取/设置它们或使用访问器。

          【讨论】:

            【解决方案10】:

            我倾向于使用带有属性初始化器的非常简单的构造函数。我发现它会导致更明确的代码。我将传递给构造函数的唯一数据是我不希望我的类的用户在创建类后更改的信息。

            【讨论】:

              【解决方案11】:

              当您通过层/层传递对象时,真正的问题出现了。例如,您创建一个人员对象并通过 Web 服务。 Web 服务有时会尝试反序列化并尝试实例化,然后如果构造函数需要参数,您可能会收到错误消息!因为 web 服务首先创建一个对象,然后分配值。

              因此,如果它是需要通过层的数据模型(类似于 POCO),我更喜欢无参数的构造函数。

              由于其他原因,构造函数参数是最好的方法(对于必填字段)。尤其是在提供类作为外部对象或程序集的接口时。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2018-06-11
                • 2022-12-03
                • 1970-01-01
                • 1970-01-01
                • 2022-12-16
                • 1970-01-01
                • 2011-11-30
                相关资源
                最近更新 更多