Рассмотрим следующий класс:
class Program
{
static void Test()
{
TestDelegate<string, int>(s => s.Length);
TestExpressionTree<string, int>(s => s.Length);
}
static void TestDelegate<T1, T2>(Func<T1, T2> del) { /*...*/ }
static void TestExpressionTree<T1, T2>(Expression<Func<T1, T2>> exp) { /*...*/ }
}
Это то, что генерирует компилятор (в несколько менее читаемом виде):
class Program
{
static void Test()
{
// The delegate call:
TestDelegate(Cache.Func ?? (Cache.Func = Cache.Instance.FuncImpl));
// The expression call:
var paramExp = Expression.Parameter(typeof(string), "s");
var propExp = Expression.Property(paramExp, "Length");
var lambdaExp = Expression.Lambda<Func<string, int>>(propExp, paramExp);
TestExpressionTree(lambdaExp);
}
static void TestDelegate<T1, T2>(Func<T1, T2> del) { /*...*/ }
static void TestExpressionTree<T1, T2>(Expression<Func<T1, T2>> exp) { /*...*/ }
sealed class Cache
{
public static readonly Cache Instance = new Cache();
public static Func<string, int> Func;
internal int FuncImpl(string s) => s.Length;
}
}
Таким образом, делегат, прошедший с первым вызовом, инициализируется один раз и повторно используется для нескольких Test
вызовов.
Однако дерево выражений, прошедшее со вторым вызовом, не используется повторно - на каждом Test
вызове инициализируется новое лямбда-выражение.
Если он не фиксирует что-либо, и деревья выражений являются неизменяемыми, какова будет проблема с кешированием дерева выражений?
редактировать
Я думаю, мне нужно уточнить, почему я думаю, что деревья выражений пригодны для кэширования.
- Полученное дерево выражений известно во время компиляции (ну, оно создается компилятором).
- Они неизменны. Таким образом, в отличие от примера массива, указанного в X39 ниже, дерево выражений не может быть изменено после его инициализации и, следовательно, безопасно кэшироваться.
- В базе кода может быть только так много деревьев выражений. Опять же, я говорю о тех, которые могут быть кэшированы, то есть те, которые инициализируются с помощью лямбда-выражений (а не тех, которые создаются вручную), не захватывая никаких внешних состояние/переменная. Аналогичным примером будет автоинтерминирование строковых литералов.
- Они предназначены для прохождения - их можно скомпилировать для создания делегата, но это не их основная функция. Если кто-то хочет скомпилированный делегат, он может просто принять один (
Func<T>
вместоExpression<Func<T>>
). Принятие дерева выражений означает, что оно будет использоваться в качестве структуры данных. Итак, "они должны быть скомпилированы в первую очередь" не является разумным аргументом против кеширования деревьев выражений.
То, что я прошу, - это потенциальные недостатки кеширования этих деревьев выражений. Требования к памяти, упомянутые svick, являются более вероятным примером.