Я пытаюсь написать какой-то волшебный код для обработки кеширования, которое может или не может быть возможным. По сути, идея состоит в том, чтобы иметь класс CacheManager со статическим методом, который принимает Func для выполнения в качестве параметра. В теле статического метода он сможет выполнить этот Func и кэшировать результаты, используя ключ кеша, который однозначно идентифицирует внутренние компоненты переданного Func (анонимный метод с 0 или более параметрами). Последующие вызовы этого статического метода с теми же предоставленными аргументами приведут к тому же ключу кеширования и вернут кешированные результаты.
Мне нужен способ однозначной идентификации переданной анонимной функции.
Изменить: Expression предоставил ответ после того, как я скорректировал синтаксис анонимной функции.
Меня беспокоит влияние на производительность компиляции выражения во время выполнения. Учитывая, что это попытка поддержки кеширования для повышения производительности, было бы глупо, если бы компиляция занимала какое-либо значительное количество времени. Есть предположения?
Базовый репозиторий для тестирования:
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
}
public class ProductRepository
{
private List<Product> products { get; set; }
public ProductRepository()
{
products = new List<Product>() { new Product() { ID = 1, Name = "Blue Lightsaber" }, new Product() { ID = 2, Name = "Green Lightsaber" }, new Product() { ID = 3, Name = "Red Lightsaber" } };
}
public Product GetByID(int productID)
{
return products.SingleOrDefault(p => p.ID == productID);
}
}
CacheManager:
public class CacheManager
{
public static TResult Get<TResult>(Expression<Func<TResult>> factory)
{
if (factory == null) throw new ArgumentNullException("factory");
var methodCallExpression = factory.Body as MethodCallExpression;
if (methodCallExpression == null) throw new ArgumentException("factory must contain a single MethodCallExpression.");
string cacheKey = "|Repository:" + methodCallExpression.Method.DeclaringType.FullName + "|Method:" + methodCallExpression.Method.Name + "|Args";
foreach (var arg in methodCallExpression.Arguments)
{
cacheKey += ":" + (arg is ConstantExpression ? ((ConstantExpression)arg).Value : Expression.Lambda(arg).Compile().DynamicInvoke());
}
if (HttpContext.Current.Cache[cacheKey] == null)
{
HttpContext.Current.Cache[cacheKey] = factory.Compile().Invoke();
}
return (TResult)HttpContext.Current.Cache[cacheKey];
}
}
Использование:
ProductRepository productRepository = new ProductRepository();
int productID = 1;
Product product;
// From repo
product = CacheManager.Get<Product>(() => productRepository.GetByID(1));
// From cache
product = CacheManager.Get<Product>(() => productRepository.GetByID(productID));
out
, чтобы уменьшить вероятность передачи чего-то изменчивого в качестве действия (при условии, что это синтаксис, необходимый для достижения окончательного решения). - person benmccallum   schedule 15.01.2013Func<TRepository, TResult>
и не заменитьGetValue
на эту подпись:TResult GetValue<TRepository, TResult>(TRepository repository, Func<TRepository, TResult> valueFactory)
? Это действительно сработает, а ваш текущий код - нет. - person Daniel Hilgarth   schedule 15.01.2013Func<TRepository, TResult>
? Если я заключу его вExpression
, я получу доступ к Body, но я не могу передать Func как Body, потому что выражение lamba с телом оператора не может быть преобразовано в дерево выражений. Кроме того, даже если бы я мог обернуть его, мне бы пришлось вызвать Compile () дляExpression
перед его вызовом; который, как я полагаю, имеет некоторые накладные расходы, делающие его менее производительным (хотя его нужно будет скомпилировать только при промахе кеша). Мысли? - person benmccallum   schedule 15.01.2013(productsRepositoryInstance) => productsRepositoryInstance.GetByID(1)
. Я думаю, вы не сможете обойтись без вызоваCompile
, если вы не хотите сравнивать код IL, чтобы получить ключ кеширования. Однако использование кода IL может быть альтернативой: когда вы не используете выражение, вы не можете вызватьMethod.GetMethodBody().GetILasByteArray()
для делегата. Я предполагаю, что этот массив байтов можно использовать в качестве ключа. Однако я не уверен, насколько эффективен ключ такого размера. - person Daniel Hilgarth   schedule 15.01.2013(arg as ConstantExpression).Value
выдастNullReferenceException
, еслиarg
не являетсяConstantExpression
. Код, выдающийNullReferenceException
, считается ошибочным. Он должен выдатьArgumentException
, сообщающий вызывающему, что все параметры должны быть константами. Что, кстати, кажется странным требованием. А что насчетr => r.GetByID(id)
? - person Daniel Hilgarth   schedule 15.01.2013