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();
}
}
}
精彩评论