【问题标题】:Private constructor in C#C#中的私有构造函数
【发布时间】:2013-07-07 18:25:25
【问题描述】:

我正在阅读 Beginning Visual C# 2012

考虑:

using System;
using System.Collections.Generic;
using System.Text;

namespace Ch10Ex01
{
    class MyClass
    {
        public readonly string Name;
        private int intVal;

        public int Val
        {
            get { return intVal; }
            set
            {
                if (0 <= value && value <= 10)
                    intVal = value;
                else
                    throw (new ArgumentOutOfRangeException("Val", value,
                        "Val must be assigned a value between 0 and 10."));
            }
        }

        public override string ToString()
        {
            return "Name: " + Name + "\nVal: " + Val;
        }

        private MyClass() : this("Default Name")
        {
        }

        public MyClass(string newName)
        {
            Name = newName;
            intVal = 0;
        }
    }
}

书中的解释:请注意,我已使用 this(“默认名称”)来确保 Name 在此构造函数被调用时获得一个值,如果该类用于派生一个新类,则这是可能的。这是必要的,因为不为 Name 字段分配值可能会导致以后出现错误。

让我感到困惑的是:它是“私有”的,如何在派生类中使用它?这(“默认名称”)是什么意思?对象如何获取“默认名称”作为其名称?

【问题讨论】:

  • "查看 cmets"。什么cmets?

标签: c# constructor private


【解决方案1】:

让我感到困惑的是:它是“私有”的,如何在派生类中使用它?这(“默认名称”)是什么意思? **对象如何获取“默认名称”作为其名称?

你的疑惑是对的!

该代码示例根本不调用默认构造函数 - 因为它是私有的,所以在不使用反射的情况下没有其他任何东西可以调用它(甚至不是派生类;对于派生类,它至少必须是 protected调用它 - 或者派生类必须嵌套在基类中)。

在示例代码中,对象没有将“默认名称”作为其值。

所以这是书中的错字或错误。

本书所描述的正确解决方案是:

  1. 完全省略默认构造函数。
  2. 在字段范围内初始化Name。这确保了无论在此类或任何派生类中编写什么其他构造函数,都不会初始化失败。

像这样:

class MyClass
{
    public readonly string Name = "Default Name";
    private int intVal;

    public int Val
    {
        get
        {
            return intVal;
        }
        set
        {
            if (0 <= value && value <= 10)
                intVal = value;
            else
                throw (new ArgumentOutOfRangeException("Val", value,
                    "Val must be assigned a value between 0 and 10."));
        }
    }
    public override string ToString()
    {
        return "Name: " + Name + "\nVal: " + Val;
    }

    public MyClass(string newName)
    {
        Name = newName;
        intVal = 0;
    }
}

请注意,声明一个由其他构造函数调用的私有默认构造函数通常很有用 - 但声明类必须实际使用它

另请注意,如果您在基类中声明了非默认构造函数并且根本不声明默认构造函数,则任何派生类必须调用现有的基类构造函数之一。

例如给定上面的类定义,那么下面的两个类声明都会导致编译错误MyClass' does not contain a constructor that takes 0 arguments:

class MyDerivedClass1: MyClass
{
    public MyDerivedClass1()  // Compile error
    {
    }
}

class MyDerivedClass2: MyClass
{
    // No constructor declared at all. Also a compile error.
}

要修复错误,MyDerivedClass 必须调用现有的构造函数:

class MyDerivedClass: MyClass
{
    public MyDerivedClass(): base("My new name")
    {
    }
}

那么私有构造函数有什么用

一个相当典型的用途是将通用初始化代码放入默认构造函数中。但是,有时您不希望调用者能够默认构造类型 - 在这种情况下,您可以将默认构造函数设为私有。

这样,您仍然可以使用默认构造函数进行通用初始化,但您会阻止类外的代码执行此操作,例如:

class Test
{
    public readonly int IntValue;
    public readonly string StringValue;

    private Test()
    {
        // Do common initialisation.
    }

    public Test(int intValue): this()
    {
        IntValue = intValue;
    }

    public Test(string stringValue): this()
    {
        StringValue = stringValue;
    }
}

通常您可以只使用私有 init() 方法来进行通用初始化,但如果您正在初始化 readonly 字段,您必须使用构造函数来执行此操作。在这种情况下,必须使用私有构造函数而不是 init() 方法。

私有默认构造函数的另一个用途是完全阻止类型的任何实例化(您只需声明私有默认构造函数,根本没有其他构造函数)。

在 .Net 1.x 中这是这样做的唯一方法 - 但 .Net 的后续版本引入了静态类,在大多数情况下无需为该类型使用私有构造函数。

您还可以声明一个私有构造函数来强制使用静态工厂方法来实例化类型。


为了完整起见,这里有一个人为的示例,演示了如何从嵌套派生类调用私有构造函数

class OuterClass
{
    public readonly string Value;

    private OuterClass(): this("Default Value")
    {
    }

    public OuterClass(string value)
    {
        Value = value;
    }

    public OuterClass GetInnerClass()
    {
        return new InnerClass();
    }

    private class InnerClass: OuterClass
    {
    }
}

使用该类定义,以下代码将打印“默认值”:

OuterClass test = new OuterClass("Test");
Console.WriteLine(test.GetInnerClass().Value);

就我个人而言,我从来不需要编写从其包含类派生的嵌套类,但如果您出于某种原因需要这样做,这是可能的。

【讨论】:

  • 太棒了,你是最好的兄弟。非常感谢!
  • 像往常一样,使用反射将不可能变为可能:System.Runtime.Serialization.FormatterServices.GetUninitializedObject 绕过构造函数和字段初始值设定项。
【解决方案2】:

因为这个类有一个private 构造函数,所以可以调用构造函数的唯一方法是在这个特定的类中。甚至派生类都不能调用它(如果它是protected 那么它可以)。正如您的代码示例所示,这毫无用处。

它是调用另一个默认构造函数的原因只是一种在用户不知道/关心它是什么的情况下拥有硬编码默认值的方式。这类似于 .NET 4.0 中函数的默认参数。

对我来说,将两个构造函数都公开会更有意义,因为我没有看到任何使用默认构造函数的代码(如果你已经向我们展示了所有代码)。


使用私有构造函数有一些正当的理由,特别是静态函数或子类。举个例子:

public class OuterClass
{
    private OuterClass() {  }

    public static OuterClass GetOuter() { return new OuterClass(); }   
}

这样,你只能从静态方法中创建一个类的新实例。

【讨论】:

  • 你的说法不太正确。如果派生类嵌套在基类中,则派生类可以调用私有构造函数。
  • @EricLippert,你是对的,这就是为什么我提到子类(嵌套类)作为一个例外。当我想到派生类时,我会想到不同代码文件中的类。
  • 您误用了“子类”一词。子类是派生类——它的超类是它的基类。嵌套类是词法上位于另一个类内部的类——它们通常被称为“内部”和“外部”类,但要小心。许多人所说的“内部类”是指 Java 风格的内部类,绑定到外部类的实例。默认情况下,C# 不支持这种内部类。
【解决方案3】:

私有构造函数意味着用户不能直接实例化一个类。相反,您可以使用命名构造函数 Idiom 之类的东西创建对象,其中您有可以创建和返回类实例的静态类函数。可以使用私有构造函数来控制singleton pattern中常用的Object的实例化。

您必须阅读单例模式才能理解私有构造函数的用途。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-16
    相关资源
    最近更新 更多