从一个构造函数调用另一个构造函数

1314

我有两个构造函数,它们分别向只读字段提供值。

public class Sample
{
    public Sample(string theIntAsString)
    {
        int i = int.Parse(theIntAsString);
        _intField = i;
    }

    public Sample(int theInt) => _intField = theInt;
    public int IntProperty    => _intField;

    private readonly int _intField;
}

有一个构造函数直接接收值,另一个则进行一些计算并获取值,然后设置字段。

现在问题来了:

  1. 我不想复制设置代码。在这种情况下,只设置一个字段,但当然可能有多个字段。
  2. 为了使字段只读,我需要从构造函数中设置它们,因此我无法将共享代码“提取”到实用程序函数中。
  3. 我不知道如何从一个构造函数调用另一个构造函数。

有什么想法吗?

14个回答

2067

就像这样:

public Sample(string str) : this(int.Parse(str)) { }

70
@Avi:你可以创建一个“静态”方法来操作参数。 - SLaks
24
我可以知道这个的执行顺序吗?Sample(string) 中的所有内容将首先被执行,然后是 Sample(int) 或者整数版本将首先被执行,然后再回到字符串版本?(就像在 Java 中调用 super() 一样?) - Rosdi Kasim
27
基类构造函数始终先运行。只有在基类初始化完成之后,您才能使用或看到 this - SLaks
5
可以使用?:或调用静态方法。 - SLaks
5
是的,他想在调用另一个构造函数之前运行代码(对参数进行操作)。此时还没有实例。 - SLaks
显示剩余9条评论

203

如果您想要的东西没有办法在没有将初始化放在自己的方法中情况下满意地实现(例如,因为您希望在初始化代码之前执行太多操作,或者将其包装在try-finally中等),则可以使所有构造函数通过引用将只读变量传递给初始化程序,然后初始化程序将能够随意操纵它们。

public class Sample
{
    private readonly int _intField;
    public int IntProperty => _intField; 

    private void setupStuff(ref int intField, int newValue) => intField = newValue;

    public Sample(string theIntAsString)
    {
        int i = int.Parse(theIntAsString);
        setupStuff(ref _intField,i);
    }

    public Sample(int theInt) => setupStuff(ref _intField, theInt);
}

24
+1дёӘзңҹжӯЈзҡ„и§ЈеҶіж–№жЎҲгҖӮдҪҝз”ЁbaseпјҲ...пјүжҲ–thisпјҲ...пјүпјҢжҲ‘们еҸӘиғҪжү§иЎҢйқһеёёжңүйҷҗзҡ„ж“ҚдҪңгҖӮ - shashwat
5
使用 out 关键字替代 ref 怎么样? - Jeppe Stig Nielsen
1
在这种特定情况下,如果该方法只需要存储某些内容,则很可能是适当的。另一方面,在调用者将某些内容放入字段并调用可能需要修改它的方法的情况下,可以使用相同的方法;在示例中使用 out 可能不太清楚。此外,我通常对 out 持怀疑态度,因为没有保证带有 out 参数的外部方法实际上会将任何内容存储在那里。 - supercat
10
因为如果变量是“readonly”,它就无法这样做。 - supercat
3
@JohnCarpenter:如果只需要设置一个readonly字段,则设置它的代码可以调用该方法并使用返回值分配该字段,但任意数量的字段都可以直接使用ref写入。此外,如果有关系,通过ref参数进行的更改会立即生效,甚至在函数返回之前,而使用函数返回值进行的更改则不会。 - supercat
显示剩余5条评论

78

在构造函数的主体之前,使用以下任一方式:

: base (parameters)

: this (parameters)

例子:

public class People: User
{
   public People (int EmpID) : base (EmpID)
   {
      // Add more statements here.
   }
}

20
很遗憾,如果我需要在构造函数调用之间对参数进行操作,这种方法就行不通了。 - Denis
1
@Denis,你不能在中间链接一个构造函数来达到相同的效果吗? - danyim
6
在调用构造函数之前,您无法执行任何操作。如果您想在初始化对象属性之前做一些事情,请将初始化移动到除构造函数之外的方法中,例如 init()。您可以从任何一个构造函数中调用此方法。 - Abdullah Shoaib
1
当你需要调用父构造函数时,@AbdullahShoaib无法使用此方法。 - Hutch Moore

15

我正在对supercat的答案进行改进。我想以下内容也可以实现:

class Sample
{
    private readonly int _intField;
    public int IntProperty
    {
        get { return _intField; }
    }

    void setupStuff(ref int intField, int newValue)
    {
        //Do some stuff here based upon the necessary initialized variables.
        intField = newValue;
    }

    public Sample(string theIntAsString, bool? doStuff = true)
    {
        //Initialization of some necessary variables.
        //==========================================
        int i = int.Parse(theIntAsString);
        // ................
        // .......................
        //==========================================

        if (!doStuff.HasValue || doStuff.Value == true)
           setupStuff(ref _intField,i);
    }

    public Sample(int theInt): this(theInt, false) //"false" param to avoid setupStuff() being called two times
    {
        setupStuff(ref _intField, theInt);
    }
}

4
可能会允许第三方通过调用 new Sample(str, false) 来创建一个样本,而无需对其进行设置。请注意,在此过程中不会改变原始含义。 - Teejay
1
这个无法编译。 - kayleeFrye_onDeck
1
这不是好的方法;令人困惑;过于复杂。如果你使用 this 调用另一个构造函数,那么让该构造函数调用 setupStuff;移除最后一个构造函数中对 setupStuff 的调用。然后你就不需要 doStuff / false 参数了。(更小的抱怨是,如果你确实有理由使用 doStuff 参数,那么将其作为可空布尔值 bool? 没有任何好处。只需使用 bool 即可。)此外,Teejay指出的意味着这是一个致命的缺陷设计。 - ToolmakerSteve
1
更好的代码可能是:public Sample(string theIntAsString) : this(int.Parse(theIntAsString)) {} public Sample(int theInt) { setupStuff(ref _intField, theInt); } 需要注意的是,第一个构造函数调用另一个构造函数时,不会调用setupStuff - ToolmakerSteve

10

这里有一个示例,调用了另一个构造函数,然后检查它所设置的属性。

    public SomeClass(int i)
    {
        I = i;
    }

    public SomeClass(SomeOtherClass soc)
        : this(soc.J)
    {
        if (I==0)
        {
            I = DoSomethingHere();
        }
    }

1
如果您在某些情况下使用默认构造函数,并对其他情况进行小的/特定更改,则可能会更加清晰。 - Adam Tolley

9

是的,在调用base或this之前,您可以调用其他方法!

public class MyException : Exception
{
    public MyException(int number) : base(ConvertToString(number)) 
    {
    }

    private static string ConvertToString(int number) 
    { 
      return number.toString()
    }

}

3
为了回答整个问题,如果你的构造函数需要初始化任何只读字段,你不能使用方法来实现。 - lentinant

8
构造函数链:即您可以使用“Base”表示父类关系,“This”表示同一类,当您想在单个调用中调用多个构造函数时使用。
  class BaseClass
{
    public BaseClass():this(10)
    {
    }
    public BaseClass(int val)
    {
    }
}
    class Program
    {
        static void Main(string[] args)
        {
            new BaseClass();
            ReadLine();
        }
    }

5
当您从基类继承一个类时,您可以通过实例化派生类来调用基类构造函数。
class sample
{
    public int x;

    public sample(int value)
    {
        x = value;
    }
}

class der : sample
{
    public int a;
    public int b;

    public der(int value1,int value2) : base(50)
    {
        a = value1;
        b = value2;
    }
}

class run 
{
    public static void Main(string[] args)
    {
        der obj = new der(10,20);

        System.Console.WriteLine(obj.x);
        System.Console.WriteLine(obj.a);
        System.Console.WriteLine(obj.b);
    }
}

下面是一个示例程序的输出结果:

50 10 20


你还可以使用this关键字从另一个构造函数中调用构造函数。

class sample
{
    public int x;

    public sample(int value) 
    {
        x = value;
    }

    public sample(sample obj) : this(obj.x) 
    {
    }
}

class run
{
    public static void Main(string[] args) 
    {
        sample s = new sample(20);
        sample ss = new sample(s);

        System.Console.WriteLine(ss.x);
    }
}

这个示例程序的输出结果是:

20

该程序涉及IT技术。

2

错误处理和让你的代码可重用是关键。我添加了字符串转整数的验证,如果需要,也可以添加其他类型。使用更具可重用性的解决方案来解决这个问题可能是这样的:

public class Sample
{
    public Sample(object inputToInt)
    {
        _intField = objectToInt(inputToInt);
    }

    public int IntProperty => _intField;

    private readonly int _intField;
}

public static int objectToInt(object inputToInt)
{
    switch (inputToInt)
        {
            case int inputInt:
                return inputInt;
            break;
            case string inputString:
            if (!int.TryParse(inputString, out int parsedInt))
            {
                throw new InvalidParameterException($"The input {inputString} could not be parsed to int");
            }
            return parsedInt;

            default:
                throw new InvalidParameterException($"Constructor do not support {inputToInt.GetType().Name}");
            break;
        }
}

展示一种不同的方法并不能回答问题。而且如果有十几个不同的字段和不同的设置方式呢?objectToInt也无法解决这个问题。 - undefined

1
在我的情况下,我有一个主构造函数,它使用OracleDataReader作为参数,但我想使用不同的查询来创建实例:
我有以下代码:
public Subscriber(OracleDataReader contractReader)
    {
        this.contract = Convert.ToString(contractReader["contract"]);
        this.customerGroup = Convert.ToString(contractReader["customerGroup"]);
        this.subGroup = Convert.ToString(contractReader["customerSubGroup"]);
        this.pricingPlan= Convert.ToString(contractReader["pricingPlan"]);
        this.items = new Dictionary<string, Member>();
        this.status = 0;
        
        
    }

因此,我创建了以下构造函数:

public Subscriber(string contract, string customerGroup) : this(getSubReader(contract, customerGroup))
    { }

还有这种方法:

 private static  OracleDataReader getSubReader(string contract, string customerGroup)
    { 
        cmdSubscriber.Parameters[":contract"].Value = contract + "%";
        cmdSubscriber.Parameters[":customerGroup"].Value = customerGroup+ "%";
        return  cmdSubscriber.ExecuteReader();
        
    }

注:在代码的其他地方定义了一个静态定义的cmdSubscriber;为了说明简化,我的主构造函数已经简化。


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