如何继承构造函数?

Ian*_*oyd 181 c# inheritance constructor

想象一下具有许多构造函数和虚方法的基类

public class Foo
{
   ...
   public Foo() {...}
   public Foo(int i) {...}
   ...
   public virtual void SomethingElse() {...}
   ...
}
Run Code Online (Sandbox Code Playgroud)

现在我想创建一个覆盖虚方法的后代类:

public class Bar : Foo 
{
   public override void SomethingElse() {...}
}
Run Code Online (Sandbox Code Playgroud)

而另一个后代做了更多的东西:

public class Bah : Bar
{
   public void DoMoreStuff() {...}
}
Run Code Online (Sandbox Code Playgroud)

我是否真的必须将所有构造函数从Foo复制到Bar和Bah?然后,如果我在Foo中更改构造函数签名,我是否必须在Bar和Bah中更新它?

有没有办法继承构造函数?有没有办法鼓励代码重用?

Jef*_*tes 122

是的,您必须实现对每个派生有意义的构造函数,然后使用该base关键字将该构造函数指向适当的基类或this关键字,以将构造函数指向同一个类中的另一个构造函数.

如果编译器对继承构造函数做出了假设,那么我们将无法正确确定对象的实例化方式.在大多数情况下,您应该考虑为什么您有这么多构造函数并考虑将它们减少到基类中的一个或两个.然后,派生类可以使用常量值来掩盖其中的一些,null并且仅通过其构造函数公开必要的值.

更新

在C#4中,您可以指定默认参数值并使用命名参数使单个构造函数支持多个参数配置,而不是每个配置都有一个构造函数.

  • 我认为必须复制一个构造函数的签名太多了.在我的实际情况中,我有一个构造函数,我不想复制它.然后我必须在两个地方改变它. (12认同)
  • 不可见的基类构造函数是C#语言的设计者(很可能是其他CLR语言)做出选择的结果.令人遗憾的是,它与应用程序的前期设计无关,但是语言的局限性使得基类中的许多构造函数的设计(通常)不可行. (11认同)
  • 对我来说最烦人的是自定义异常 - `Sub New()`,`Sub New(Message As String)`,`Sub New(Message As String,InnerEx as Exception)`,`Sub New(Info As Serialization.SerializationInfo) ,Context As Serialization.StreamingContext)`... Yawn` (9认同)
  • 在这种情况下,你是对的,尽管现在"最佳实践"成为"最佳实践". (3认同)

Kon*_*Kon 70

387个构造函数?? 那是你的主要问题.相反怎么样?

public Foo(params int[] list) {...}
Run Code Online (Sandbox Code Playgroud)

  • 我想这样可以鼓励人们为提问者没有想到要提出的更高层次的问题提供更好的解决方案,而不是用它来贬低它们. (56认同)
  • 宾果,人们不尽可能多地使用参数 (5认同)
  • 如果只有一个构造函数,你的答案会改变吗? (3认同)
  • 说实话,我从来没有在任何生产代码中使用它们.我通常有List <int>或我传递的一些集合.我渴望利用params的那一天.:) (2认同)
  • 这实际上只有在参数类型相同时才有效,并用作初始化对象的列表.但是如果构造函数甚至有不同的签名(比如一个需要两个整数,另一个是两个字符串,另一个是int和一个字符串,第四个是浮点数和枚举.你将花费大部分构造函数检查类型和unbox - 在运行时做的事情,应该由编译器完成. (2认同)

Jam*_*ran 42

是的,你必须复制所有387个构造函数.您可以通过重定向来重复使用它们:

  public Bar(int i): base(i) {}
  public Bar(int i, int j) : base(i, j) {}
Run Code Online (Sandbox Code Playgroud)

但那是你能做的最好的事情.


小智 34

太糟糕了,我们被迫告诉编译器显而易见:

Subclass(): base() {}
Subclass(int x): base(x) {}
Subclass(int x,y): base(x,y) {}
Run Code Online (Sandbox Code Playgroud)

我只需要在12个子类中做3个构造函数,所以这没什么大不了的,但我不太喜欢在每个子类上重复这个,因为我不习惯这么长时间写它.我确信这是有道理的,但我认为我从未遇到过需要这种限制的问题.

  • Resharper让这很容易!(Alt + Insert,Constructors) (15认同)

dvi*_*oen 27

不要忘记,您也可以在相同的继承级别将构造函数重定向到其他构造函数:

public Bar(int i, int j) : this(i) { ... }
                            ^^^^^
Run Code Online (Sandbox Code Playgroud)


小智 10

另一个简单的解决方案可能是使用包含参数作为属性的结构或简单数据类; 这样你可以提前设置所有默认值和行为,将"参数类"作为单个构造函数参数传递:

public class FooParams
{
    public int Size...
    protected myCustomStruct _ReasonForLife ...
}
public class Foo
{
    private FooParams _myParams;
    public Foo(FooParams myParams)
    {
          _myParams = myParams;
    }
}
Run Code Online (Sandbox Code Playgroud)

这避免了多个构造函数(有时)的混乱,并提供了强大的类型,默认值以及参数数组未提供的其他好处.它还可以很容易地继续发展,因为从Foo继承的任何内容仍然可以根据需要访问甚至添加到FooParams.你仍然需要复制构造函数,但是你总是(大部分时间)只(作为一般规则)(至少现在)需要一个构造函数.

public class Bar : Foo
{
    public Bar(FooParams myParams) : base(myParams) {}
}
Run Code Online (Sandbox Code Playgroud)

我真的很喜欢重载的Initailize()和类工厂模式方法更好,但有时你只需要一个智能构造函数.只是一个想法.


Kei*_*ith 7

作为Foo一个类,你能不能创建虚拟重载Initialise()方法?那么它们可用于子类并且仍可扩展吗?

public class Foo
{
   ...
   public Foo() {...}

   public virtual void Initialise(int i) {...}
   public virtual void Initialise(int i, int i) {...}
   public virtual void Initialise(int i, int i, int i) {...}
   ... 
   public virtual void Initialise(int i, int i, ..., int i) {...}

   ...

   public virtual void SomethingElse() {...}
   ...
}
Run Code Online (Sandbox Code Playgroud)

除非你有很多默认属性值并且你经常遇到它,否则这应该没有更高的性能成本.


Pav*_*man 6

public class BaseClass
{
    public BaseClass(params int[] parameters)
    {

    }   
}

public class ChildClass : BaseClass
{
    public ChildClass(params int[] parameters)
        : base(parameters)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)


Aar*_*oyd 5

我个人认为这在Microsofts部分是一个错误,它们应该允许程序员覆盖基类中的构造函数,方法和属性的可见性,然后使它成为总是继承构造函数.

这样我们只是简单地覆盖(具有较低的可见性 - 即私有)我们不想要的构造函数,而不必添加我们想要的所有构造函数.德尔福这样做,我想念它.

例如,如果要覆盖System.IO.StreamWriter类,则需要将所有7个构造函数添加到新类中,如果您想要注释,则需要使用标题XML对每个构造函数进行注释.更糟糕的是,元数据视图dosnt将XML注释作为正确的XML注释,因此我们必须逐行进行复制并粘贴它们.微软在这里想什么?

我实际上已经编写了一个小实用程序,您可以粘贴元数据代码,并使用过度可见性将其转换为XML注释.

  • 这个问题不是可见性问题.除了从派生类构造函数调用之外,子类构造函数不能用于构建派生类对象.需要什么IMHO是一个工具,通过它,子类可以指定对于所有父类构造函数,编译器应该自动生成具有相同参数的子类构造函数,该构造函数调用适当的父构造函数,然后调用通用子代. - 类构造函数模板. (4认同)

T.J*_*der 5

我真的必须将所有构造函数从 in 复制FooBarandBah吗?然后,如果我更改了构造函数签名Foo,是否必须在Bar和 中更新它Bah

是的,如果您使用构造函数来创建实例。

有没有办法继承构造函数?

不。

有没有办法鼓励代码重用?

好吧,我不会讨论继承构造函数是好事还是坏事,以及它是否会鼓励代码重用,因为我们没有它们,我们也不会得到它们。:-)

但是在 2014 年,使用当前的 C#,您可以通过使用泛型方法来获得非常类似于继承构造函数的东西create。它可以成为您随身携带的有用工具,但您不会轻易伸手去拿它。我最近在面临需要将某些内容传递给数百个派生类中使用的基类型的构造函数时使用它(直到最近,基类不需要任何参数,因此默认构造函数很好 - 派生类类根本没有声明构造函数,而是获得了自动提供的构造函数)。

它看起来像这样:

// In Foo:
public T create<T>(int i) where: where T : Foo, new() {
    T obj = new T();
    // Do whatever you would do with `i` in `Foo(i)` here, for instance,
    // if you save it as a data member;  `obj.dataMember = i;`
    return obj;
}
Run Code Online (Sandbox Code Playgroud)

也就是说,您可以create使用类型参数调用泛型函数,该类型参数是Foo具有零参数构造函数的任何子类型。

然后,而不是做Bar b new Bar(42),你会这样做:

var b = Foo.create<Bar>(42);
// or
Bar b = Foo.create<Bar>(42);
// or
var b = Bar.create<Bar>(42); // But you still need the <Bar> bit
// or
Bar b = Bar.create<Bar>(42);
Run Code Online (Sandbox Code Playgroud)

在那里我已经展示了直接打开的create方法Foo,但当然它可以在某种工厂类中,如果它设置的信息可以由该工厂类设置。

只是为了清楚起见:名称create并不重要,它可以是makeThingy您喜欢的任何其他名称。

完整示例

using System.IO;
using System;

class Program
{
    static void Main()
    {
        Bar b1 = Foo.create<Bar>(42);
        b1.ShowDataMember("b1");

        Bar b2 = Bar.create<Bar>(43); // Just to show `Foo.create` vs. `Bar.create` doesn't matter
        b2.ShowDataMember("b2");
    }

    class Foo
    {
        public int DataMember { get; private set; }

        public static T create<T>(int i) where T: Foo, new()
        {
            T obj = new T();
            obj.DataMember = i;
            return obj;
        }
    }

    class Bar : Foo
    {
        public void ShowDataMember(string prefix)
        {
            Console.WriteLine(prefix + ".DataMember = " + this.DataMember);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)