В C #, как я могу создать переменную типа значения во время выполнения? - PullRequest
0 голосов
/ 10 января 2019

Я пытаюсь реализовать метод, подобный:

(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), создать переменную времени выполнения и создать метод получения и установки с константой, являющейся начальным значением.

1 Ответ

0 голосов
/ 04 февраля 2019

Я согласен с некоторыми комментаторами вопроса, что деревья выражений кажутся ненужными, поэтому вот простая реализация показанного API без них:

struct Box<T> {
    public T Value;
}

(Func<T> getFn, Action<T> setFn) MakePair<T>(T initialVal) {
    var box = new Box<T> { Value = initialVal };
    return (() => box.Value, v => box.Value = v);
}

В качестве ответа на поставленный вопрос (как определить dynref без лямбды), что-то не так со следующими модификациями dynvar и dynref?

var dynvar = new T[] { initialVal };
var dynref = Expression.Constant(dynvar);
...