基类构造函数的虚函数

时间:2011-02-21 17:16:10

标签: c#

我在以下代码段

中总结了我的问题
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace St
{
    public  class Animal
    {
        public Animal()
        {
            Speak();
        }
      public virtual void Speak()
      {
          Console.WriteLine("Animal speak");
      }
    }
    public  class Dog:Animal
    {
        private StringBuilder sb = null;

        public Dog()
        {
                sb=new StringBuilder();
        }
        public override void Speak()
        {
            Console.WriteLine("bow...{0}",sb.Append("bow"));
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Dog d=new Dog();
        }
    }
}

当我编译它时没有错误但是当我运行它时我得到了对象引用错误。

4 个答案:

答案 0 :(得分:11)

问题是你在Animal的构造函数中调用了一个虚方法。

这是一种危险的做法 - 正是出于这个原因。问题是,当你构造一个Dog时,“Animal”(基类)构造函数首先运行 。此时,它会调用Speak()。但是,Dog的Speak方法依赖于已运行的Dog的构造函数,因此sb尚未初始化且仍为null

通常,构造函数中的虚方法调用是一个非常糟糕的想法 - 并且是设计缺陷的标志。我在这里推荐一种不同的方法。


我对此进行重新修改的建议是简单地从构造函数中删除Speak()。我会把这段代码写成:

public class Animal
{
    public Animal()
    {
        // Don't do this in the constructor
        // Speak();
    }
    public virtual void Speak()
    {
        Console.WriteLine("Animal speak");
    }
}
public class Dog : Animal
{
    private StringBuilder sb = null;

    public Dog()
    {
        sb = new StringBuilder();
    }
    public override void Speak()
    {
        Console.WriteLine("bow...{0}", sb.Append("bow"));
    }
}
class Program
{
    static void Main(string[] args)
    {
        Dog d = new Dog();
        d.Speak();
    }
}

从逻辑的角度来看,这对我来说更有意义。这一行:

Dog d = new Dog();

负责单一操作 - 构建新的Dog。作为班级的消费者,我不希望它执行复杂的操作(即:说话) - 仅仅是为了正确地创建和设置Dog及其内部状态。

当我想要它发言时,我特意打电话:

d.Speak();

答案 1 :(得分:4)

这种情况正在发生,因为当您调用Dog构造函数时,首先调用Animal构造函数,然后调用Speak(),但sb尚未初始化。

答案 2 :(得分:2)

Speak初始化之前,从基类构造函数调用

sb。您需要做的是在需要之前初始化StringBuilder类。

更新

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

namespace St
{
    public  class Animal
    {
        public Animal()
        {
            Speak();
        }
      public virtual void Speak()
      {
          Console.WriteLine("Animal speak");
      }
    }
    public  class Dog:Animal
    {
        private StringBuilder sb = null;

        public Dog()
        {  }
        public override void Speak()
        {
            if(sb==null) { sb = new StringBuilder(); }    // lazy init
            sb.Append("bow");
            Console.WriteLine("bow...{0}",sb.ToString());
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Dog d=new Dog();
        }
    }
}

答案 3 :(得分:2)

基础Animal构造函数将在Dog的构造函数之前被调用(因此是虚拟Dog :: Speak),这意味着在您初始化某人之前。

取决于您的确切设计:

1.将sb移动到你的基类

2.检查Dog :: Speak中的null sb(以及那里的init sb)

3.将代码从构造函数移出到Init(推荐)