在对象Listener
的构造函数中,我们接受一个实参并订阅它的一个事件。如果在订阅事件后在构造函数中抛出异常,则在引发事件时仍然调用OnSomethingChanged()
方法-即使对象没有成功构造,而且据我所知,没有实例存在。
现在我可以通过明显地重构设计来修复这个问题,但是我更感兴趣的是为什么即使构造函数没有成功完成也会调用实例方法?如果该方法使用了任何在异常之前没有初始化的局部变量,那么很明显,它会出现BOOM!
class Program
{
static void Main(string[] args)
{
Input input = new Input();
try
{
new Listener(input);
}
catch (InvalidOperationException)
{
// swallow
}
input.ChangeSomething(); // prints "Something changed!"
}
}
public class Listener
{
public Listener(Input input)
{
input.SomethingChanged += OnSomethingChanged; // subscibe
throw new InvalidOperationException(); // do not let constructor succeed
}
void OnSomethingChanged(object sender, EventArgs e)
{
Console.WriteLine("Something changed!");
}
}
public class Input
{
public event EventHandler SomethingChanged;
public void ChangeSomething()
{
SomethingChanged(this, EventArgs.Empty);
}
}
虽然从构造函数抛出异常意味着实例可能最终处于不完整状态,但这样做并不会阻止实例本身被创建并存储在内存中(因为这发生在调用其构造函数之前)。
此外,在抛出异常时事件处理程序已经被绑定,因此引发事件将导致调用该处理程序。
快速说明第一点,如果你给Listener
一个字段在它的构造函数中初始化,然后试图在抛出异常后初始化它(这显然是行不通的):
string foo;
public Listener(Input input, string f)
{
input.SomethingChanged += OnSomethingChanged;
// Because this is thrown...
throw new InvalidOperationException();
// ... this never happens
foo = f;
}
然后尝试在其OnSomethingChanged
处理程序中访问它:
void OnSomethingChanged(object sender, EventArgs e)
{
Console.WriteLine("Listener.foo = " + foo);
}
无论如何调用new Listener(...)
,输出都将是
只是因为侦听器没有机会初始化它的foo
字段。虽然它没有完全初始化,但就分配而言,它仍然是一个完整的对象。