关于C#:如何继承构造函数?

How to inherit constructors?

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

1
2
3
4
5
6
7
8
9
public class Foo
{
   ...
   public Foo() {...}
   public Foo(int i) {...}
   ...
   public virtual void SomethingElse() {...}
   ...
}

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

1
2
3
4
public class Bar : Foo
{
   public override void SomethingElse() {...}
}

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

1
2
3
4
public class Bah : Bar
{
   public void DoMoreStuff() {...}
}

我真的需要把所有的构造器从foo复制到bar和bah吗?然后,如果我在foo中更改了一个构造函数签名,我需要在bar和bah中更新它吗?

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


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

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

更新

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


387名施工人员??这是你的主要问题。改成这个怎么样?

1
public Foo(params int[] list) {...}


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

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

但这是你能做的最好的。


太糟糕了,我们不得不告诉编译器:

1
2
3
Subclass(): base() {}
Subclass(int x): base(x) {}
Subclass(int x,y): base(x,y) {}

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


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

1
2
public Bar(int i, int j) : this(i) { ... }
                            ^^^^^

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

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FooParams
{
    public int Size...
    protected myCustomStruct _ReasonForLife ...
}
public class Foo
{
    private FooParams _myParams;
    public Foo(FooParams myParams)
    {
          _myParams = myParams;
    }
}

这避免了多个构造函数(有时)的混乱,并提供了强类型、默认值和参数数组没有提供的其他好处。它还可以很容易地向前推进,因为从foo继承的任何东西仍然可以根据需要进入甚至添加fooparams。您仍然需要复制构造函数,但是(大多数情况下)您总是(作为一般规则)只需要(至少现在)一个构造函数。

1
2
3
4
public class Bar : Foo
{
    public Bar(FooParams myParams) : base(myParams) {}
}

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


由于Foo是一个类,您不能创建虚拟重载的Initialise()方法吗?那么它们将可用于子类,并且仍然是可扩展的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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() {...}
   ...
}

这不应该有更高的性能成本,除非你有很多默认的属性值,而且你点击了很多。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BaseClass
{
    public BaseClass(params int[] parameters)
    {

    }  
}

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

    }
}

我个人认为这在Microsoft方面是一个错误,他们应该允许程序员重写基类中构造函数、方法和属性的可见性,然后使之成为构造函数总是被继承的对象。

通过这种方式,我们只需覆盖(具有较低的可见性,即private)我们不想要的构造函数,而不必添加我们想要的所有构造函数。德尔菲这样做,我很想。

例如,如果要重写System.IO.StreamWriter类,则需要将所有7个构造函数添加到新类中;如果要注释,则需要使用头XML对每个构造函数进行注释。更糟糕的是,元数据视图没有将XML注释作为适当的XML注释,因此我们必须逐行复制和粘贴它们。微软在想什么?

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


Do i really have to copy all constructors from Foo into Bar and Bah? And then if i change a constructor signature in Foo, do i have to update it in Bar and Bah?

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

Is there no way to inherit constructors?

不。

Is there no way to encourage code reuse?

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

但是在2014年,使用当前的C,您可以通过使用通用的create方法来获得与继承的构造函数非常相似的东西。它可能是一个有用的工具,在你的腰带,但你不会轻易触及它。最近,当我遇到需要将一些东西传递到几百个派生类中使用的基类型的构造函数中时(直到最近,基不需要任何参数,因此默认的构造函数很好;派生类根本不声明构造函数,并获得了自动提供的构造函数)。

看起来是这样的:

1
2
3
4
5
6
7
// 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)

1
2
3
4
5
6
7
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或其他你喜欢的名称。

完整例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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);
        }
    }
}


问题不是bar和bah必须复制387个构造函数,而是foo有387个构造函数。foo显然做了太多的事情-快速重构!另外,除非您有一个很好的理由在构造函数中设置值(如果您提供一个无参数的构造函数,您可能不会),否则我建议您使用属性获取/设置。


不,您不需要将所有387个构造函数复制到BAR和BAH。BAR和BAH可以有任意多或任意少的构造器,这与您在foo上定义的构造器数量无关。例如,您可以选择只有一个bar构造函数,它用foo的212th构造函数构造foo。

是的,您在BAR或BAH所依赖的foo中更改的任何构造函数都将要求您相应地修改BAR和BAH。

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


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


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