The Problem
Recently I was told to refactor i.e. move some initialization logic from the constructor. I came up with something similar to the posted code. In actual there are bunch of loader classes inheriting from ILoader and doing all kind of stuff (TPL, DB access,XML parsing, CSV Writing) One of our UBER Developers reviewed my code and give some really appreciative comments particularly with the line of code marked with //<--:
Wow – I have seen a lot but that would make the http://thedailywtf.com/ for sure. HOW can you think of having a Getter method have a initialization SIDE EFFECT??
What is your opinion guys. What should I've done to avoid such appreciative comments?
The Solution
using System;
public class Program
{
public static void Main()
{
var obj = new TestLoader("testing");
Console.WriteLine("Calling Run() first time....");
obj.Run();
Console.WriteLine("Calling Run() again...");
obj.Run();
Console.WriteLine("Calling SomeOtherMethod()...");
obj.SomeOtherMethod();
}
}
//Class to make sure initialize will only be called once
public abstract class DataInitializer<T>
{
public bool IsInitialized { get { return _isInitialized.Value; } }//<--
readonly Lazy<bool> _isInitialized;
protected DataInitializer(T param)
{
Console.WriteLine("Inside DataInitializer constructor");
_isInitialized = new Lazy<bool>(Initialize);
}
protected abstract bool Initialize();
}
//Some loader class
public class TestLoader :DataInitializer<string>, ILoader
{
public TestLoader(string test):base(test)
{
}
protected override bool Initialize()
{
Console.WriteLine("Inside Initialize()");
return true;
}
public void Run()
{
if(IsInitialized)
{
Console.WriteLine("Inside Run()");
}
}
public void SomeOtherMethod()
{
if(IsInitialized)
{
Console.WriteLine("Inside SomeOtherMethod()");
}
}
}
interface ILoader
{
void Run();
void SomeOtherMethod();
}
3 Answers 3
Your code does feel like a misuse of lazy evaluation. Some alternative approaches to consider:
Make
Initialize
method public and throw if it was not called:public override void Initialize() { //do stuff _isInitialized = true; } public void Run() { if(!_isInitialized) throw new InvalidOperationException(); //do stuff }
IMHO, it is all-around best option, as it allows you to control when to do initialization.
Call initialization automatically on every
Run
call:protected override void Initialize() { if (_isInitialized) return; //do stuff _isInitialized = true; } public void Run() { Initialize(); //do stuff }
This approach is good in some cases. For example, when you have an unstable network connection, it makes sense to check the connection before every method call, and reconnect if needed.
Use
Lazy
objects to initialize actual services:public CsvWriter CsvWriter { get { return _csvWriter.Value; } } readonly Lazy<CsvWriter> _csvWriter; public void Run() { CswWriter.Write(...) }
I'm not a fan of lazy initialization, but it is fine, if you use it to instantiate one or two actual services. Laziness is justified in scenarios when there are high chances that
Run
method will not be called at all by your software.
P.S. Note, that Lazy
is thread-safe, while options #1 and #2 are not and require synchronization if thread-safety is required.
You should listen to your collegue. This is a horrible idea. Let me quote MSDN about Properties (C# Programming Guide):
A property is a member that provides a flexible mechanism to read, write, or compute the value of a private field. Properties can be used as if they are public data members, but they are actually special methods called accessors. This enables data to be accessed easily and still helps promote the safety and flexibility of methods.
This means it should not have any side effects. Properties are expected to be cheap and their purpose is to access data (quickly). If an operation can take some time it should be implemented as a method. On the other hand the IsInitilized
property would never return a false
so it's actually useless too. You could never tell if an object is not-initialized.
-
\$\begingroup\$ The
Result
property of a Task also takes some time, i.e. it blocks the calling thread. There is a difference nevertheless, since the result of a task is not alwaystrue
as here. It would be good if the answer offered an alternative approach \$\endgroup\$WorldSEnder– WorldSEnder2017年06月09日 11:39:33 +00:00Commented Jun 9, 2017 at 11:39
Yes, it is an absolutely horrible idea to have the IsInitialized
getter call Initialize()
!
Aside from the options mentioned in NikitaB's answer, another option would be to wrap the Initialize()
call in the base-class so you can set IsInitialized
there:
public abstract class DataInitializer<T>
{
public bool IsInitialized { get; private set; }
protected abstract void InitializeImpl();
public void Initialize()
{
if(!IsInitialized) // lock if thread-safety is required
{
InitializeImpl();
IsInitialized = true;
}
}
}
I'm assuming DataInitializer<T>
contains more code than this in real life - if this is literally all DataInitializer<T>
does, you are basically just reimplementing Lazy<T>
as a base-class.