我在以下代码段
中总结了我的问题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();
}
}
}
当我编译它时没有错误但是当我运行它时我得到了对象引用错误。
答案 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(推荐)