189

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

public class Foo
{
   ...
   public Foo() {...}
   public Foo(int i) {...}
   ...
   public virtual void SomethingElse() {...}
   ...
}

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

public class Bar : Foo 
{
   public override void SomethingElse() {...}
}

还有另一个做更多事情的后代:

public class Bah : Bar
{
   public void DoMoreStuff() {...}
}

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

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

4

14 回答 14

127

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

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

更新

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

于 2008-10-21T19:01:56.723 回答
69

387个构造函数??那是你的主要问题。这个怎么样?

public Foo(params int[] list) {...}
于 2008-10-21T19:03:30.293 回答
42

是的,您必须复制所有 387 个构造函数。您可以通过重定向它们来进行一些重用:

  public Bar(int i): base(i) {}
  public Bar(int i, int j) : base(i, j) {}

但这是你能做的最好的。

于 2008-10-21T19:03:09.590 回答
35

太糟糕了,我们不得不告诉编译器显而易见的事情:

Subclass(): base() {}
Subclass(int x): base(x) {}
Subclass(int x,y): base(x,y) {}

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

于 2009-04-11T05:50:18.737 回答
27

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

public Bar(int i, int j) : this(i) { ... }
                            ^^^^^
于 2008-10-21T20:49:53.587 回答
10

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

public class FooParams
{
    public int Size...
    protected myCustomStruct _ReasonForLife ...
}
public class Foo
{
    private FooParams _myParams;
    public Foo(FooParams myParams)
    {
          _myParams = myParams;
    }
}

这避免了多个构造函数(有时)的混乱,并提供了强类型、默认值和参数数组不提供的其他好处。这也使得继承变得容易,因为从 Foo 继承的任何东西仍然可以根据需要到达,甚至添加到 FooParams。您仍然需要复制构造函数,但您总是(大部分时间)仅(作为一般规则)曾经(至少,目前)需要一个构造函数。

public class Bar : Foo
{
    public Bar(FooParams myParams) : base(myParams) {}
}

我真的更喜欢重载的 Initialize() 和 Class Factory Pattern 方法,但有时你只需要一个智能构造函数。只是一个想法。

于 2011-11-15T22:14:58.637 回答
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() {...}
   ...
}

这不应该有更高的性能成本,除非您有很多默认属性值并且您经常使用它。

于 2008-10-21T21:03:46.680 回答
6
public class BaseClass
{
    public BaseClass(params int[] parameters)
    {

    }   
}

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

    }
}
于 2010-09-30T14:20:40.047 回答
5

我个人认为这是微软的一个错误,他们应该允许程序员覆盖基类中构造函数、方法和属性的可见性,然后使其始终继承构造函数。

这样,我们只需简单地覆盖(以较低的可见性 - 即私有)我们不想要的构造函数,而不必添加我们想要的所有构造函数。Delphi 就是这样做的,我很怀念它。

举个例子,如果你想重写 System.IO.StreamWriter 类,你需要将所有 7 个构造函数添加到你的新类中,如果你喜欢注释,你需要用标题 XML 注释每个构造函数。更糟糕的是,元数据视图不会将 XML 注释作为正确的 XML 注释,因此我们必须逐行复制并粘贴它们。微软在这里是怎么想的?

我实际上编写了一个小实用程序,您可以在其中粘贴元数据代码,它会使用覆盖的可见性将其转换为 XML 注释。

于 2011-07-21T23:49:42.757 回答
5

我真的必须将所有构造函数从 and 复制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;
}

也就是说,您可以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);

我已经展示了直接开启的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);
        }
    }
}
于 2014-07-14T18:36:45.800 回答
3

问题不在于 Bar 和 Bah 必须复制 387 个构造函数,问题在于 Foo 有 387 个构造函数。Foo 显然做了太多的事情 - 快速重构!此外,除非您有充分的理由在构造函数中设置值(如果您提供无参数构造函数,则可能不会),我建议您使用属性获取/设置。

于 2008-10-21T19:08:19.877 回答
2

不,您不需要将所有 387 个构造函数复制到 Bar 和 Bah。Bar 和 Bah 可以拥有任意数量的构造函数,与在 Foo 上定义的数量无关。例如,您可以选择仅使用一个 Bar 构造函数来使用 Foo 的第 212 个构造函数构造 Foo。

是的,您在 Foo 中更改 Bar 或 Bah 所依赖的任何构造函数都需要您相应地修改 Bar 和 Bah。

不,.NET 中没有办法继承构造函数。但是您可以通过在子类的构造函数中调用基类的构造函数或调用您定义的虚拟方法(如 Initialize())来实现代码重用。

于 2008-10-21T19:15:58.550 回答
1

您可能能够适应C++ 虚拟构造函数 idiom的一个版本。据我所知,C# 不支持协变返回类型。我相信这在许多人的愿望清单上。

于 2008-10-21T19:17:04.163 回答
0

太多的构造函数是设计失败的标志。更好的类具有很少的构造函数和设置属性的能力。如果您确实需要控制属性,请考虑在同一名称空间中创建工厂并将属性设置器设置为内部。让工厂决定如何实例化类并设置其属性。工厂可以有一些方法,这些方法可以根据需要使用尽可能多的参数来正确配置对象。

于 2008-10-21T19:05:58.413 回答