开发者

Virtual functions from base-class constructors

I have summarized my problem in following code snippet

using System;
using System.Collections.Generic;
using System.Linq;
using S开发者_JAVA百科ystem.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();
        }
    }
}

When I compile it it there is no error but when I ran it I am getting object reference error.


The problem is that you're calling a virtual method in your constructor for Animal.

This is a dangerous practice - exactly for this reason. The issue is that, when you construct a Dog, the "Animal" (base class) constructor is run first. At this point, it calls Speak(). However, Dog's Speak method relies on Dog's constructor having been run, so sb hasn't been initialized and is still null.

In general, virtual method calls in a constructor are a very bad idea - and a sign of a design flaw. I'd recommend a different approach here.


My recommendation for reworking this would be to simply remove the Speak() from the constructor. I would write this code as:

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();
    }
}

This makes more sense to me, from a logic standpoint as well. This line:

Dog d = new Dog();

Is responsible for a single action - constructing a new Dog. I would not expect it, as a consumer of the class, to perform complex operations (ie: speaking) - merely to create and setup a Dog and its internal state correctly.

When I want it to speak, I specifically call:

d.Speak();


This is happening because when you call the Dog constructor, the Animal constructor is called first, which then calls Speak(), but sb hasn't been initialized yet.


The base Animal constructor will be called (and thus the virtual Dog::Speak) before the constructor for the Dog, meaning before you init the sb.

Depending on your exact design either:

1.Move the sb to your base class

2.Check for null sb in Dog::Speak (and init sb there)

3.Move the code out from the constructor into an Init (recommended)


Speak is called from the base class constructor before sb has been intialized. What you need to do is initiaze the StringBuilder class just before it is needed.

Update

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();
        }
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜