In my project, I have an abstract Cache class that allows me to populate a series of lists that globally persist throughout my application. These cache objects are thread-safe and can be manipulated as necessary, and allow for me to cut-down on the massive overhead of querying external third-party APIs directly. I've seen some serious hate for singletons, so I'm a bit curious what other options I have when this is my current use case.I've seen dependency injection mentioned quite a bit, but I don't know if it's quite adequate or useful in this scenario.Here is an example of my Cache abstract class:public abstract class Cache<TU, T>    where TU : Cache<TU, T>, new()    where T : class{    private static readonly TU Instance = new TU();    private static volatile State _currentState = State.Empty;    private static volatile object _stateLock = new object();    private static volatile object _dataLock = new object();    private static DateTime _refreshedOn = DateTime.MinValue;    private static T InMemoryData { get; set; }    public static T Data    {        get        {            switch (_currentState)            {                case State.OnLine:                    var timeSpentInCache = (DateTime.UtcNow - _refreshedOn);                    if (timeSpentInCache > Instance.GetLifetime())                    {                        lock (_stateLock)                        {                            if (_currentState == State.OnLine) _currentState = State.Expired;                        }                    }                    break;                case State.Empty:                    lock (_dataLock)                    {                        lock (_stateLock)                        {                            if (_currentState == State.Empty)                            {                                InMemoryData = Instance.GetData();                                _refreshedOn = DateTime.UtcNow;                                _currentState = State.OnLine;                            }                        }                    }                    break;                case State.Expired:                    lock (_stateLock)                    {                        if (_currentState == State.Expired)                        {                            _currentState = State.Refreshing;                            Task.Factory.StartNew(Refresh);                        }                    }                    break;            }            lock (_dataLock)            {                if (InMemoryData != null) return InMemoryData;            }            return Data;        }    }    public static T PopulateData()    {        return Data;    }    protected abstract T GetData();    protected virtual TimeSpan GetLifetime()    {        return TimeSpan.FromMinutes(10);    }    private static void Refresh()    {        if (_currentState != State.Refreshing) return;        var dt = Instance.GetData();        lock (_stateLock)        {            lock (_dataLock)            {                _refreshedOn = DateTime.UtcNow;                _currentState = State.OnLine;                InMemoryData = dt;            }        }    }    public static void Invalidate()    {        lock (_stateLock)        {            _refreshedOn = DateTime.MinValue;            _currentState = State.Expired;        }    }    private enum State    {        Empty,        OnLine,        Expired,        Refreshing    }}And an example of its implementation.public class SalesForceCache{    public class Users : Cache<Users, List<Contact>>    {        protected override List<Contact> GetData()        {            var sf = new SalesForce();            var users = sf.GetAllUsers();            sf.Dispose();            return users;        }        protected override TimeSpan GetLifetime()        {            try            {                return TimeSpan.FromDays(1);            }            catch (StackOverflowException)            {                return TimeSpan.Zero;            }        }    }    public class Accounts : Cache<Accounts, List<Account>>    {        protected override List<Account> GetData()        {            var sf = new SalesForce();            var accounts = sf.GetAllAccounts();            sf.Dispose();            return accounts;        }        protected override TimeSpan GetLifetime()        {            try            {                return TimeSpan.FromDays(1);            }            catch (StackOverflowException)            {                return TimeSpan.Zero;            }        }    }}
Alternatives to Singletons for caching lists of data?
c#;design patterns;object oriented;singleton