I have written following code snippet:
public interface IModelToViewModelServiceBase<in TDomain, out TViewModel>
where TDomain : class, IDataModel
where TViewModel : class, IDataModelViewModel
{
TViewModel GetViewModel(TDomain model);
}
public abstract class ModelToViewModelServiceBase<TDomain, TViewModel> : IModelToViewModelServiceBase<TDomain, TViewModel>
where TDomain : class, IDataModel
where TViewModel : class, IDataModelViewModel
{
private readonly IDictionary<TDomain, TViewModel> _modelToViewModel
= new Dictionary<TDomain, TViewModel>();
protected abstract TViewModel Create(TDomain model);
public TViewModel GetViewModel(TDomain model)
{
if (model == null) return null;
if (!_modelToViewModel.ContainsKey(model))
_modelToViewModel[model] = Create(model);
return _modelToViewModel[model];
}
}
The purpose of this class doesn't matter for the problem. On rare occasions I get a KeyNotFound on the return-line. However, to my understanding the preceding if-clauses should prevent this from happening. No key can possibly be null and the retrieved value if not existing before was added in the previous instruction.
What am I missing here?
I now developed a workaround:
public interface IModelToViewModelServiceBase<in TDomain, out TViewModel>
where TDomain : class, IDataModel
where TViewModel : class, IDataModelViewModel
{
TViewModel GetViewModel(TDomain model);
}
public abstract class ModelToViewModelServiceBase<TDomain, TViewModel> : IModelToViewModelServiceBase<TDomain, TViewModel>
where TDomain : class, IDataModel
where TViewModel : class, IDataModelViewModel
{
private readonly IDictionary<TDomain, TViewModel> _modelToViewModel
= new Dictionary<TDomain, TViewModel>();
protected abstract TViewModel Create(TDomain model);
public TViewModel GetViewModel(TDomain model)
{
if (model == null) return null;
TViewModel viewModel = null;
if (!_modelToViewModel.ContainsKey(model))
{
viewModel = Create(model);
_modelToViewModel[model] = viewModel;
}
else
viewModel = _modelToViewModel[model];
return viewModel;
}
}
This seems to work. However, this workaround shouldn't be necessary. May be this workaround is even better, because now one less access to the dictionary is executed. Still, the previous version should have worked always.
Update after answer:
@evk and @mjwills are both right. I didn't consider that my code is unsafe for concurrent use and multiple threads are accessing it. Hence, afer the suggestions of @mjwills the code looks as follows:
public interface IModelToViewModelServiceBase<in TDomain, out TViewModel>
where TDomain : class, IDataModel
where TViewModel : class, IDataModelViewModel
{
TViewModel GetViewModel(TDomain model);
}
public abstract class ModelToViewModelServiceBase<TDomain, TViewModel> : IModelToViewModelServiceBase<TDomain, TViewModel>
where TDomain : class, IDataModel
where TViewModel : class, IDataModelViewModel
{
private readonly ConcurrentDictionary<TDomain, TViewModel> _modelToViewModel
= new ConcurrentDictionary<TDomain, TViewModel>();
protected abstract TViewModel Create(TDomain model);
public TViewModel GetViewModel(TDomain model)
{
if (model == null) return null;
return _modelToViewModel.GetOrAdd(model, Create); ;
}
}
You mentioned that your code can be accessed from multiple threads, but your code is not thread-safe. Is is not safe to write and read to regular Dictionary
from multiple threads (while if you only read and never write - then it's ok). You might think that if you never remove items from dictionary then KeyNotFoundException
might not be thrown in your case but that is not true. When you use structure in a way it was not designed to - anything can happen. For example consider this code:
class Program
{
public static void Main(string[] args)
{
var service = new ModelToViewModelServiceBase();
new Thread(() => AddServices(service)).Start();
new Thread(() => AddServices(service)).Start();
new Thread(() => AddServices(service)).Start();
new Thread(() => AddServices(service)).Start();
Console.ReadKey();
}
private static void AddServices(ModelToViewModelServiceBase services) {
for (int i = 0; i < 100000; i++) {
services.GetViewModel(i);
}
}
}
public class ModelToViewModelServiceBase {
private readonly IDictionary<int, string> _modelToViewModel
= new Dictionary<int, string>();
protected string Create(int model) {
return model.ToString();
}
public string GetViewModel(int model) {
if (!_modelToViewModel.ContainsKey(model))
_modelToViewModel[model] = Create(model);
return _modelToViewModel[model];
}
}
When you run it - you will almost always get KeyNotFoundException
, while you never remove items from dictionary. That's because of how Dictionary
implemented internally, and I think exact details are not relevant to this question.
Long story short - just don't use non-thread-safe structure from multiple threads (except when all threads are only reading and never writing) without proper synchronization, even if it might feel to you that it will work fine.