如何在 C# 中进行构造函数链接

Posted

技术标签:

【中文标题】如何在 C# 中进行构造函数链接【英文标题】:How to do constructor chaining in C# 【发布时间】:2010-12-21 08:37:48 【问题描述】:

我知道这应该是一个超级简单的问题,但我已经为这个概念苦苦挣扎了一段时间。

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

我正在上我的第一个 OOP 课程,所以我只是在学习。我不明白构造函数链接是如何工作的或如何实现它,甚至为什么它比只做没有链接的构造函数更好。

我希望能提供一些带有解释的示例。

那么如何链接它们呢? 我知道有两个:

public SomeClass this: 0

public SomeClass

    someVariable = 0
 

但是你怎么用三个、四个等等呢?

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

【问题讨论】:

【参考方案1】:

您使用标准语法(像方法一样使用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()

对于“为什么?”:

代码减少(总是一件好事)

需要调用一个非默认的基础构造函数,例如:

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; 。只是寻找最好的逻辑方式来链接它们,感谢任何想法。 有没有办法在调用另一个链式构造函数之前在第一个构造函数中操作要调用的构造函数的参数值?【参考方案2】:

我只想向任何搜索此内容的人提出一个有效的观点。如果您打算使用 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 年以来一直在抱怨构造链接并且无法使用默认的可选参数!现在它在开发世界中花了很长时间,开发人员害怕使用它,因为它不会向后兼容。

【讨论】:

如果您使用这种技术,您必须注意默认参数是在编译时设置在 caller 中的,而不是 callee 中。这意味着如果您在库中部署这样的代码并且应用程序使用具有默认参数的构造函数;如果默认参数发生变化,您需要使用库重新编译应用程序。由于这个问题,有些人认为公共接口中的默认参数本身就很危险。 默认参数方法的另一个缺点是,如果你在构造函数中有两个默认参数,你不能只用第二个调用它。在此处的示例中,您将遇到以下编译错误:Foo myFooOne = new Foo(""); 如果您的构造函数之一依赖于依赖注入,这也是一个问题 如果你需要依赖注入,正如 John Arundell 所指出的,错误消息将是“参数必须是编译器时间常数”;为了规避这种情况,请使用旧的链接方式,或传递一个 Options 对象,如 Jon Skeet's answer。【参考方案3】:

最好用一个例子来说明这一点。成像我们有一个类 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 从属性更改为内部字段,则只需更改一个构造函数 - 如果您在所有三个构造函数中设置该属性这将是三个地方来改变它。

【讨论】:

【参考方案4】:

“构造函数链”有什么用? 您可以使用它从另一个构造函数调用一个构造函数。

如何实现“构造函数链”? 在定义构造函数后使用“: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;
    

【讨论】:

【参考方案5】:

我有一个日记课,所以我不会一次又一次地写设置值

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;

【讨论】:

【参考方案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;
    


【讨论】:

【参考方案7】:

所有这些答案都很好,但我想补充一点关于具有更复杂初始化的构造函数。

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";
    

尽管没有这个静态函数也可以解决这个例子,静态函数允许更复杂的逻辑,甚至可以从其他地方调用方法。

【讨论】:

谢谢!正是所有其他答案中缺少的示例。【参考方案8】:

我希望下面的示例能够对构造函数链接有所帮助。 例如,我的用例在这里,您希望用户将目录传递给您的 构造函数,用户不知道要传递哪个目录并决定让 您分配默认目录。您加强并分配您认为的默认目录 会工作的。 顺便说一句,如果您想知道 *.Dump() 是什么,我在此示例中使用了 LINQPad。干杯

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;
    

【讨论】:

【参考方案9】:

在构造函数链中还有一个重要的点:顺序。 为什么? 假设您有一个在运行时由一个期望它是默认构造函数的框架构造的对象。如果您希望能够在传递值的同时仍然能够在需要时传递构造函数参数,那么这非常有用。

例如,我可以有一个支持变量,它被我的默认构造函数设置为默认值,但可以被覆盖。

public class MyClass

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

【讨论】:

以上是关于如何在 C# 中进行构造函数链接的主要内容,如果未能解决你的问题,请参考以下文章

如何在 c# 中调用与构造函数不同的构造函数? [复制]

自动属性无法在 C# 实例构造函数中初始化

C# 静态构造函数

如何更改 C# Record 构造函数的行为

C#在自己的构造函数之后调用基类的构造函数?

如何在 c# 中基于构造函数方法创建数组?