• Я сделал Code Review, может быть я где-то ошибся или у вас есть что добавить?

    @achird
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    
    //EXTERNAL CODE
    //Приведен только для справки, никак нельзя менять, код обфусцирован, исходников нет
    public sealed class ExternalDataProvider : IDisposable
    {
        public extern int LongRunningCalculation(int value, int value2);
        public extern void Dispose();
    }
    
    /// <summary>
    /// Объект-значение
    /// <para>Базовый класс</para>
    /// </summary>
    public abstract class ValueObject<T> where T : ValueObject<T>
    {
        public static bool operator ==(ValueObject<T> value1, ValueObject<T> value2)
        {
            if ((object)value1 == null)
                return (object)value2 == null;
    
            return value1.Equals(value2);
        }
    
        public static bool operator !=(ValueObject<T> value1, ValueObject<T> value2)
        {
            return !(value1 == value2);
        }
    
        public override bool Equals(object obj)
        {
            var valueObject = obj as T;
    
            if (ReferenceEquals(valueObject, null))
                return false;
    
            return CompareValues(valueObject);
        }
    
        public override int GetHashCode()
        {
            return GetValueHashCode();
        }
    
        /// <summary>
        /// Получить HashCode
        /// </summary>
        /// <returns></returns>
        protected abstract int GetValueHashCode();
    
        /// <summary>
        /// Сравнить значения
        /// </summary>
        /// <returns></returns>
        protected abstract bool CompareValues(T other);
    }
    
    /// <summary>
    /// Параметр для DataProvider
    /// </summary>
    public class Parameter : ValueObject<Parameter>
    {
        private Parameter()
        {
            FirstValue = -1;
            SecondValue = -1;
        }
        public Parameter(int firstValue, int secondValue)
        {
            if (firstValue > 100 || firstValue < 0 || secondValue < 1 || secondValue > 12)
                throw new ArgumentException("Is not supported values");
            FirstValue = firstValue;
            SecondValue = secondValue;
        }
        /// <summary>
        /// Первый параметр
        /// </summary>
        public int FirstValue { get; }
        /// <summary>
        /// Второй параметр
        /// </summary>
        public int SecondValue { get; }
    
        /// <summary>
        /// Default value
        /// </summary>
        public static readonly Parameter Empty = new Parameter();
        protected override bool CompareValues(Parameter other)
        {
            return FirstValue == other.FirstValue && SecondValue == other.SecondValue;
        }
        protected override int GetValueHashCode()
        {
            return (FirstValue, SecondValue).GetHashCode();
        }
    }
    
    /// <summary>
    /// Результат для DataProvider
    /// </summary>
    public class Result : ValueObject<Result>
    {
        private Result()
        {
            // Default value
            Value = default;
        }
        public Result(int value)
        {
            Value = value;
        }
        /// <summary>
        /// Результат
        /// </summary>
        public int Value { get; }
    
        /// <summary>
        /// Default value
        /// </summary>
        public static readonly Result Empty = new Result();
        protected override bool CompareValues(Result other)
        {
            return Value == other.Value;
        }
        protected override int GetValueHashCode()
        {
            return Value.GetHashCode();
        }
    }
    
    /// <summary>
    /// Интерфейс DataProvider
    /// </summary>
    public interface IDataProvider
    {
        public Result GetResult(Parameter parameter);
    }
    
    /// <summary>
    /// Получить данные из ExternalDataProvider без кеша
    /// </summary>
    public class DataProvider : IDataProvider
    {
        public Result GetResult(Parameter parameter)
        {
            using var externalDataProvider = new ExternalDataProvider();
            return new Result (externalDataProvider.LongRunningCalculation(parameter.FirstValue, parameter.SecondValue));
        }
    }
    
    /// <summary>
    /// DataProvider с кешированием данных
    /// Реализация кеша не имеет принципиального значения 
    /// </summary>
    public class CacheDataProvider : IDataProvider
    {
        private readonly IDataProvider dataProvider;
        private readonly ConcurrentDictionary<Parameter, Result> cache = new();
        public CacheDataProvider(IDataProvider dataProvider)
        {
            this.dataProvider = dataProvider;
        }
        public Result GetResult(Parameter parameter)
        {
            return cache.GetOrAdd(parameter, (p => dataProvider.GetResult(p)));
        }
    }


    Реализация доступа к DataProvider с использованием ValueObject для параметра и результата. Доступ к данным осуществляется через интерфейс IDataProvider. Реализация кеширования данных через декоратор. Для рабочего кода можно сделать GetResultAsync.
    Ответ написан
    Комментировать