Confusion about when protected and private constructors are called
I'm working on a legacy project that has heavy use of Singletons. While most of them would be better implemented otherwise, for now the goal is to get them under test. As such, I have the following structure.
public class SomeSingleton
{
private Dependency someDependency;
public static readonly SomeSingleton Instance = new SomeSingleton();
static SomeSingleton() {}
private SomeSingleton()
{
someDependency = new Dependency();
}
}
So, to make it testable without losing the 'Singelton'ness', I've tried to add a protected constructor that takes the dependency as a parameter to call from an inheriting class that I can run tests through. I realize that making it open for inheritance is breaking the Singleton pattern as well, but it is only used by the test framework and not done, by convention, in the production code.
Like so:
public class SomeSingleton
{
private Dependency someDependency;
public static readonly SomeSingleton Instance = new SomeSingleton();
static SomeSingleton() {}
private SomeSingleton() : this(new Dependency()) {}
protected SomeSingleton(Dependency someDependency)
{
this.someDependency = someDependency;
}
}
public class SomeSingletonTestImplementation : SomeSingleton
{
public SomeSingletonTestImplementation (Dependency someDependency)
: base (someDependency) {}
}
Finally, after the setup, the question:
With this implementation, I expected just the protected constru开发者_如何学Goctor in the original Singleton to be called from the inheritor. However, when I debug, the constructor in the inheritor is called, but then, instead of stepping into the protected constructor, the private constructor is called, and then the protected constructor.
Is there any way that the private constructor won't be called? Also, if anyone has a brief explanation of why it happens the way it does, that'd be great.
Thanks!
your static readonly instance of your singleton is what is calling the no parameter (private) constructor.
public static readonly SomeSingleton Instance = new SomeSingleton();
Subsequently you construct another instance in your test case
public SomeSingletonTestImplementation (Dependency someDependency)
: base (someDependency) {}
You can probably safely ignore the singleton instance being created, the one in your test will be the one where you've injected your dependancy.
Try commenting out the instantiation of the singleton instance; you may be seeing the private constructor being called because the static initializer of your singleton class may be called as soon as you first create an instance of the test implementation class (in turn initializing the singleton instance and calling the private constructor.)
As explained by Jamiec, it is because of the initialised field that the private constructor is called first.
The inline initialisation will be called before you step in the protected constructor, that's why when you debug the private ctor is called before the protected one.
Maybe you could simply make the Dependency field (make it a property if possible) protected so you can change it after initialization?
Thanks to the answers that pointed me in the right direction. The solution that I came to ended up being the following, which is probably truer to a typical Singleton implementation anyway.
Instead of having a public variable, I turned it into a static property that lazily evaluates the Singleton. That way, the private constructor is only called when the actual Instance property is called, which never happens from the test code, but always from the production code. Then I can happily insert mock dependencies via the protected constructor and test all the public methods via the inheriting class.
public class SomeSingleton
{
private Dependency someDependency;
private static SomeSingleton instance;
public static SomeSingleton Instance
{
return instance ?? (instance = new SomeSingleton());
}
static SomeSingleton() {}
private SomeSingleton() : this(new Dependency()) {}
protected SomeSingleton(Dependency someDependency)
{
this.someDependency = someDependency;
}
}
精彩评论