using System;
using System.Collections.Generic;
using System.Text;

namespace StatoBot.Analytics
{
	public class Statistics : Dictionary<string, decimal>
	{
		private readonly Encoding encoder;
		private readonly object mutationLock;

		public Statistics()
		{
			encoder = Encoding.GetEncoding(
				"UTF-8",
				new EncoderReplacementFallback(string.Empty),
				new DecoderExceptionFallback()
			);

			mutationLock = new object();
		}

		public void Increment(string key)
		{
			key = SanitizeKey(key);

			lock (mutationLock)
			{
				if(!ContainsKey(key))
				{
					Add(key, 1);
				}
				else
				{
					TryGetValue(key, out var count);
					Remove(key);
					Add(key, count + 1);
				}
			}
		}

		private string SanitizeKey(string key)
		{
			return encoder.GetString(encoder.GetBytes(key));
		}
	}
}

using System;
using System.Text;
using System.Collections.Concurrent;

namespace StatoBot.Analytics
{
    public class Statistics : ConcurrentDictionary<string, decimal>
    {
        private readonly Encoding encoder;

        public Statistics()
        {
            encoder = Encoding.GetEncoding(
                "UTF-8",
                new EncoderReplacementFallback(string.Empty),
                new DecoderExceptionFallback()
            );
        }

        public void Increment(string key)
        {
            key = SanitizeKey(key);

            if (!ContainsKey(key))
            {
                TryAdd(key, 1);
            }
            else
            {
                TryGetValue(key, out var count);
                TryRemove(key, out decimal _);
                TryAdd(key, count + 1);
            }
        }

        private string SanitizeKey(string key)
        {
            return encoder.GetString(encoder.GetBytes(key));
        }
    }
}

Use ConcurrentDictionary instead of Dictionary to allow lock-free async access
Use ConcurrentDictionary instead of Dictionary to allow lock-free async access
