Я пытаюсь реализовать метод, подобный:
(Func<T> getFn, Action<T> setFn) MakePair<T>(T initialVal) {
}
Он вернет две сгенерированные во время выполнения лямбды, которые получают и устанавливают динамически создаваемую переменную, используя Expression
деревья для создания кода.
Мое текущее решение состоит в том, чтобы динамически создать массив типа с одним элементом и ссылаться на него:
(Func<T> getFn, Action<T> setFn) MakePair<T>(T initialVal) {
var dynvar = Array.CreateInstance(typeof(T), 1);
Expression<Func<Array>> f = () => dynvar;
var dynref = Expression.Convert(f.Body, typeof(T).MakeArrayType());
var e0 = Expression.Constant(0);
var getBody = Expression.ArrayIndex(dynref, e0);
var setParam = Expression.Parameter(typeof(T));
var setBody = Expression.Assign(Expression.ArrayAccess(dynref, e0), setParam);
var getFn = Expression.Lambda<Func<T>>(getBody).Compile();
var setFn = Expression.Lambda<Action<T>>(setBody, setParam).Compile();
return (getFn, setFn);
}
Есть ли лучший способ создать переменную типа значения во время выполнения, которую можно прочитать / записать, чем при использовании массива?
Есть ли лучший способ ссылаться на созданный во время выполнения массив, кроме использования лямбда-выражения для создания ссылки (поля?) Для использования в вызовах метода ArrayIndex
/ ArrayAccess
?
Избыточная справочная информация
Для тех, кто интересуется, в конечном итоге это произошло в попытке создать что-то вроде Perl-самовирификации значений l для хэшей Perl.
Представьте, что у вас есть List<T>
с дублирующимися элементами и вы хотите создать Dictionary<T,int>
, который позволит вам искать количество для каждого уникального T
в списке. Вы можете использовать несколько строк кода для подсчета (в данном случае T
равно int
):
var countDict = new Dictionary<int, int>();
foreach (var n in testarray) {
countDict.TryGetValue(n, out int c);
countDict[n] = c + 1;
}
Но я хочу сделать это с LINQ, и я хочу избежать двойной индексации countDict
(что интересно, ConcurrentDictionary
имеет AddOrUpdate
для этой цели), поэтому я использую Aggregate:
var countDict = testarray.Aggregate(new Dictionary<int,int>(), (d, n) => { ++d[n]; return d; });
Но у этого есть пара проблем. Во-первых, Dictionary
не создаст значение для отсутствующего значения, поэтому вам нужен новый тип Dictionary
, который автоматически создает отсутствующие значения, например, с помощью. семя лямбда:
var countDict = testarray.Aggregate(new SeedDictionary<int, Ref<int>>(() => Ref.Of(() => 0)), (d, n) => { var r = d[n]; ++r.Value; return d; });
Но у вас все еще есть проблема с lvalue, поэтому вы заменяете простой int
счетчик классом Ref
. К сожалению, C # не может создать класс C ++ Ref
первого класса, но использование класса, основанного на автоматическом создании лямбды-установщика из лямбды-получателя (с использованием деревьев выражений), достаточно близко. (К сожалению, C # по-прежнему не принимает ++d[n].Value;
, хотя он должен быть действительным, поэтому вам нужно создать временный.)
Но теперь у вас есть проблема создания нескольких целочисленных переменных во время выполнения для хранения счетчиков. Я расширил класс Ref<>
, чтобы взять лямбду, которая возвращает константу (ConstantExpression
), создать переменную времени выполнения и создать метод получения и установки с константой, являющейся начальным значением.