如何在C#中进行构造函数链式调用

269

我知道这似乎是一个非常简单的问题,但我已经在思考这个概念有一段时间了。

我的问题是,如何在C#中链式使用构造函数?

我正在上我的第一个面向对象编程(OOP)课程,所以我正在学习。我不明白构造函数链是如何工作的,也不知道如何实现它,甚至不知道为什么它比不使用链式构造函数更好。

我希望能有一些例子和解释。

那么,如何将它们链接起来?
我知道两个构造函数的情况如下:

public SomeClass this: {0}

public SomeClass
{
    someVariable = 0
} 

但是如果是三个、四个等怎么办呢?

我知道这是一个初学者问题,但我仍然很难理解,不知道为什么。

9个回答

418

您在类内部使用标准语法(像方法一样使用this)来选择重载:

class Foo 
{
    private int id;
    private string name;

    public Foo() : this(0, "") 
    {
    }

    public Foo(int id, string name) 
    {
        this.id = id;
        this.name = name;
    }

    public Foo(int id) : this(id, "") 
    {
    }

    public Foo(string name) : this(0, name) 
    {
    }
}

那么:

Foo a = new Foo(), b = new Foo(456,"def"), c = new Foo(123), d = new Foo("abc");

注意:

  • 你可以使用base(...)在基类型上链接到构造函数
  • 你可以在每个构造函数中添加额外的代码
  • 如果您没有指定任何内容,则默认值为base()

关于“why?”:

  • 减少代码(总是好事)
  • 调用非默认基础构造函数是必要的,例如:

    SomeBaseType(int id) : base(id) {...}
    
    请注意,您也可以以类似的方式使用对象初始化程序,而无需编写任何内容:
    SomeType x = new SomeType(), y = new SomeType { Key = "abc" },
             z = new SomeType { DoB = DateTime.Today };
    

我正在进行一些链接操作,因为这个答案被投票得非常高,所以我不得不问一个问题。每个构造函数都设置已传递的属性,然后调用默认构造函数来设置其他属性会有什么缺点吗?这样,您就不需要在多处编写默认值(0"")(减少错误的机会)。例如:public Foo(int id) : this () { this.id = id; }?或者,我还考虑过:public Foo(int id) : this ("") { this.id = id; }。只是想寻找最好的逻辑方式来链接它们,非常感谢任何想法。 - Rufus L
在调用其他链接的构造函数之前,有没有一种方法可以在第一个构造函数中操作要调用的构造函数的参数值? - eaglei22
构造函数的执行顺序是什么? - Flimtix
1
@Nanoserver基础版首选 - Marc Gravell
@MarcGravell 非常感谢。我应该表述得更清楚。我指的是 this - Flimtix
被调用者先于调用者——即链式构造函数首先。 - Marc Gravell

62

对于那些正在查找此问题的人,我想提出一个有效的观点。如果你要使用 .NET 4.0 之前(VS2010)的版本,请注意你需要像上面展示的一样创建构造函数链。

然而,如果你使用的是 .NET 4.0,我有好消息。现在你可以拥有一个带有可选参数的单个构造函数!我来简化一下 Foo 类的例子:

class Foo {
  private int id;
  private string name;

  public Foo(int id = 0, string name = "") {
    this.id = id;
    this.name = name;
  }
}

class Main() {
  // Foo Int:
  Foo myFooOne = new Foo(12);
  // Foo String:
  Foo myFooTwo = new Foo(name:"Timothy");
  // Foo Both:
  Foo myFooThree = new Foo(13, name:"Monkey");
}

当你实现构造函数时,你可以使用可选参数,因为已经设置了默认值。

希望你喜欢这堂课!我简直不敢相信开发人员自2004/2005年以来一直在抱怨构造链接和不能使用默认可选参数!现在它已经在开发世界里花费了如此长的时间,以至于开发者们害怕使用它,因为它不兼容老版本。


60
如果你使用这种技术,你必须意识到默认参数是在调用者编译时设置的,而不是被调用者。这意味着如果你在一个库中部署了带有默认参数的构造函数,并且应用程序使用了它,如果默认参数发生更改,就需要重新编译使用该库的应用程序。由于这个陷阱,一些人认为公共接口中的默认参数本质上是危险的。 - Chuu
2
默认参数方法的另一个缺点是,如果您在构造函数中有两个默认参数,例如,您不能仅使用第二个参数调用它。在这个例子中,您将会得到编译错误:Foo myFooOne = new Foo(""); - dragan.stepanovic
1
如果你的构造函数依赖注入,那么这也可能是一个问题。 - John Arundell
如果您需要依赖注入,正如John Arundell所指出的那样,错误消息将是“参数必须是编译时常量”; 为了规避这个问题,可以采用旧的链接方式或传递一个Options对象,就像Jon Skeet的答案中所述。 - Marcelo Scofano Diniz

32

以下是一个例子,更能生动地说明问题。假设我们有一个名为Person的类:

public Person(string name) : this(name, string.Empty)
{
}

public Person(string name, string address) : this(name, address, string.Empty)
{
}

public Person(string name, string address, string postcode)
{
    this.Name = name;
    this.Address = address;
    this.Postcode = postcode;
}

这里我们有一个构造函数,设置了一些属性,并使用构造函数链接,允许您只使用名称创建对象,或同时使用名称和地址。如果您仅使用名称创建实例,则会向名称和地址发送默认值string.Empty,然后将默认值Postcode发送到最终的构造函数中。

这样做可以减少编写的代码量。只有一个构造函数实际上具有代码,您不会重复自己,例如,如果您将Name从属性更改为内部字段,则只需更改一个构造函数 - 如果您在所有三个构造函数中都设置该属性,则需要更改三个位置。


16

什么是“构造函数链”的作用?
它用于从一个构造函数调用另一个构造函数。

如何实现“构造函数链”?
在构造函数定义后使用“:this(yourProperties)”关键字。例如:

Class MyBillClass
{
    private DateTime requestDate;
    private int requestCount;

    public MyBillClass()
    {
        /// ===== we naming "a" constructor ===== ///
        requestDate = DateTime.Now;
    }
    public MyBillClass(int inputCount) : this()
    {
        /// ===== we naming "b" constructor ===== ///
        /// ===== This method is "Chained Method" ===== ///
        this.requestCount= inputCount;
    }
}

为什么它很有用?
重要的原因是减少编码和防止重复代码,例如初始化属性的重复代码。 假设类中的某些属性必须使用特定值进行初始化(在我们的示例中为requestDate)。而且类有2个或更多个构造函数。如果没有“构造函数链”,则必须在类的所有构造函数中重复初始化代码。

它是如何工作的?(或者说,“构造函数链”执行顺序是什么?)
在上面的示例中,方法“a”将首先被执行,然后指令序列将返回到方法“b”。 换句话说,上面的代码等同于下面的代码:

Class MyBillClass
{
    private DateTime requestDate;
    private int requestCount;

    public MyBillClass()
    {
        /// ===== we naming "a" constructor ===== ///
        requestDate = DateTime.Now;
    }
    public MyBillClass(int inputCount) : this()
    {
        /// ===== we naming "b" constructor ===== ///
        // ===== This method is "Chained Method" ===== ///

        /// *** --- > Compiler execute "MyBillClass()" first, And then continue instruction sequence from here
        this.requestCount= inputCount;
    }
}

12
我有一个日记类,因此我不需要一遍又一遍地编写设置值的代码。
public Diary() {
    this.Like = defaultLike;
    this.Dislike = defaultDislike;
}

public Diary(string title, string diary): this()
{
    this.Title = title;
    this.DiaryText = diary;
}

public Diary(string title, string diary, string category): this(title, diary) {
    this.Category = category;
}

public Diary(int id, string title, string diary, string category)
    : this(title, diary, category)
{
    this.DiaryID = id;
}

8

所有这些答案都很好,但我想在构造函数中添加一些更复杂的初始化注释。

class SomeClass {
    private int StringLength;
    SomeClass(string x) {
         // this is the logic that shall be executed for all constructors.
         // you dont want to duplicate it.
         StringLength = x.Length;
    }
    SomeClass(int a, int b): this(TransformToString(a, b)) {
    }
    private static string TransformToString(int a, int b) {
         var c = a + b;
         return $"{a} + {b} = {c}";
    }
}

虽然这个例子也可以在没有静态函数的情况下解决,但是静态函数允许更复杂的逻辑,甚至可以从其他地方调用方法。


谢谢!这正是其他答案中所缺少的例子。 - totkeks

6

您是在询问这个吗?

  public class VariantDate {
    public int day;
    public int month;
    public int year;

    public VariantDate(int day) : this(day, 1) {}

    public VariantDate(int day, int month) : this(day, month,1900){}

    public VariantDate(int day, int month, int year){
    this.day=day;
    this.month=month;
    this.year=year;
    }

}

2

构造函数链中还有一个重要的点:顺序。

为什么呢?假设你有一个对象在运行时由框架构造,该框架期望它的默认构造函数。如果你想在需要时能够传递参数值,同时仍然具有传递构造函数参数的能力,那么这是非常有用的。

例如,我可以有一个备份变量,通过我的默认构造函数设置为默认值,但具有被覆盖的能力。

public class MyClass
{
  private IDependency _myDependency;
  MyClass(){ _myDependency = new DefaultDependency(); }
  MYClass(IMyDependency dependency) : this() {
    _myDependency = dependency; //now our dependency object replaces the defaultDependency
  }
}

2

我希望以下示例能够阐明构造函数链的概念。
例如,假设你期望用户在构造函数中传入一个目录,但用户并不知道该传哪个目录,于是决定让你指定默认目录。你可以选择指定你认为适合的默认目录。

顺便说一下,我在这个示例中使用了 LINQPad ,以防你想知道 *.Dump() 是什么意思。
祝好

void Main()
{

    CtorChaining ctorNoparam = new CtorChaining();
    ctorNoparam.Dump();
    //Result --> BaseDir C:\Program Files (x86)\Default\ 

    CtorChaining ctorOneparam = new CtorChaining("c:\\customDir");
    ctorOneparam.Dump();    
    //Result --> BaseDir c:\customDir 
}

public class CtorChaining
{
    public string BaseDir;
    public static string DefaultDir = @"C:\Program Files (x86)\Default\";


    public CtorChaining(): this(null) {}

    public CtorChaining(string baseDir): this(baseDir, DefaultDir){}

    public CtorChaining(string baseDir, string defaultDir)
    {
        //if baseDir == null, this.BaseDir = @"C:\Program Files (x86)\Default\"
        this.BaseDir = baseDir ?? defaultDir;
    }
}

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