【问题标题】:How to inherit constructors?如何继承构造函数?
【发布时间】:2010-09-18 09:26:44
【问题描述】:

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

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 中更新它?

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

【问题讨论】:

    标签: c# inheritance constructor


    【解决方案1】:

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

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

    更新

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

    【讨论】:

    • 我认为必须复制一个构造函数的签名太多了。在我的真实情况下,我有一个构造函数,我不想复制它。然后我必须在两个地方进行更改。
    • 这就是前期设计很重要的原因。您不应该 100% 确信一切都必须改变。
    • 不可见的基类构造函数是 C# 语言(很可能还有其他 CLR 语言)的设计者做出选择的结果。遗憾的是,它与应用程序的前期设计无关,而是与语言的限制有关,这使得在基类中使用许多构造函数进行设计(通常)不切实际。
    • 在这种情况下你是对的,尽管现在“最佳实践”变成了“最佳可用实践”。
    • 对我来说最烦人的是自定义异常——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)...打哈欠`
    【解决方案2】:

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

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

    但这是你能做的最好的。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 宾果游戏,人们不会尽可能多地使用参数
      • 说实话,我自己从未在任何生产代码中使用过它们。我通常有 List 或我通过的一些集合。我渴望有一天我可以利用参数。 :)
      • 如果只有一个构造函数,你的答案会改变吗?
      • 我认为 SO 会鼓励人们为提问者没想到的更高级别的问题提供更好的解决方案,而不是反对他们。
      • 这只有在参数类型相同的情况下才有效,并用作初始化对象的列表。但是,如果构造函数甚至有不同的签名(比如一个需要两个整数,另一个两个字符串,另一个是整数和一个字符串,第四个是浮点数和枚举。你会花费大部分构造函数检查类型和unbox -- 在运行时做真正应该由编译器完成的事情。
      【解决方案4】:

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

      【讨论】:

      • 如果您要求您的类是不可变的,那么您不想公开属性。因为那时你的班级有状态。
      • @Daniel,这就是为什么我建议使用内部设置器的工厂模式。这样,setter 只暴露给创建对象的类,并且对象可以(出于所有意图和目的)保持其不变性。
      【解决方案5】:

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

      【讨论】:

      • 没有解决真正的问题。如果类需要不可变状态,则无济于事。
      • 因此“除非你有充分的理由”。关键是,任何拥有 387 个构造函数的类都做的太多了。
      • 如果我有 2 个构造函数,问题不变;如何解决必须复制和粘贴 2 行的问题。
      【解决方案6】:

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

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

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

      【讨论】:

      • 你不应该在构造函数中调用虚方法——子类在调用虚方法时不会运行它的构造函数。
      • 是的,这可能是个好建议。不过,您可以在构造后由调用者调用虚拟初始化模式。模式将是 Bar bar = new Bar(); bar.Initialize();
      【解决方案7】:

      您也许可以调整C++ virtual constructor idiom 的一个版本。据我所知,C# 不支持协变返回类型。我相信这在很多人的愿望清单上。

      【讨论】:

      • 这听起来很有趣,虽然我不知道协变是什么意思。 :(
      • 基本上就是说可以通过改变返回类型来覆盖一个参数相同的函数,所以"string Function(int,int)"可以和"int Function(int,int)不同”。如果你现在在 C# 中尝试这个,它会抱怨重载已经存在。
      【解决方案8】:

      别忘了,你也可以将构造函数重定向到同级继承的其他构造函数:

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

      【讨论】:

        【解决方案9】:

        由于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() {...}
           ...
        }
        

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

        【讨论】:

        • 是的,这是个好主意;虽然我可能会初始化一个返回类的静态方法
        【解决方案10】:

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

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

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

        【讨论】:

        • Resharper 让这一切变得简单! (Alt+Insert,构造函数)
        【解决方案11】:
        public class BaseClass
        {
            public BaseClass(params int[] parameters)
            {
        
            }   
        }
        
        public class ChildClass : BaseClass
        {
            public ChildClass(params int[] parameters)
                : base(parameters)
            {
        
            }
        }
        

        【讨论】:

          【解决方案12】:

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

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

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

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

          【讨论】:

          • 问题不在于可见性。子类构造函数不能用于构建派生类对象,除非从派生类构造函数调用。恕我直言,需要的是一种工具,通过该工具,子类可以指定对于所有父类构造函数,编译器应自动生成具有相同参数的子类构造函数,该构造函数调用适当的父构造函数,然后调用通用子构造函数-class 构造函数模板。
          • 显然,这个编译器选项在C++ 11中存在,但是C#已经落后了。
          【解决方案13】:

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

          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) {}
          }
          

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

          【讨论】:

            【解决方案14】:

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

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

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

            没有。

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

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

            但是在 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);
                    }
                }
            }
            

            【讨论】:

            • 我正要发布一个模板工厂函数解决方案......但是你比我早了2.5年
            猜你喜欢
            • 2013-06-01
            • 2023-04-03
            • 2021-05-21
            • 2013-03-21
            • 2011-09-09
            • 2014-11-22
            相关资源
            最近更新 更多