Можем ли мы получить личность делегата?
Можно ли создать idenitity делегата, чтобы отличить его от другого делегата? Подумайте об этом коде:
Func<int, int, int> delegate1 = a, b => a + b;
Func<int, int, int> delegate2 = a, b => a + b;
Func<int, int, int> delegate3 = a, b => a - b;
let id1 = id(delegate1);
let id2 = id(delegate2);
let id3 = id(delegate3);
Assert(id1 == id2);
Assert(id1 != id3);
Проблема, которую я хочу решить, я хочу кэшировать некоторый JIT-компилированный графический код в .NET. Чтобы упростить его использование, я хочу, чтобы пользователь послал делегата, и если делегат тот же, мы пытаемся выяснить код графического процессора из кеша, а не компилировать его каждый раз:
Parallel(outputA, inputA1, inputA2, a, b => a + b); //should JIT compile GPU code, and cache it by its identity
Parallel(outputB, inputB1, inputB2, a, b => a + b); //should NOT JIT compile GPU code, and use the cached GPU code by its identity
Одним из возможных решений является сравнение строки выражения, но у нее все еще есть проблема, чтобы поймать кузнец, например:
int c = 0;
Expression<Func<int, int, int>> delegate1 = (a, b) => a + b + c;
c += 1;
Expression<Func<int, int, int>> delegate2 = (a, b) => a + b + c;
Expression<Func<int, int, int>> delegate3 = (a, b) => a - b - c;
Console.WriteLine(delegate1);
Console.WriteLine(delegate2);
Console.WriteLine(delegate1.ToString() == delegate2.ToString());
Console.ReadKey();
Как указано в @SWeko и @Luaan, в приведенном выше примере delegate1
и delegate2
на самом деле одинаковы. Но цель кэширования делегата заключается в следующем использовании:
int c = 1;
Parallel(outputA, inputA1, inputA2, (a,b) => a+b); //do JIT compile of GPU code
c += 1;
Parallel(outputB, inputB1, inputB2, (a,b) => a+b); //as the delegate is same then the previouse one, it will not do JIT GPU code compiling, but then, that is wrong!
Ответы
Ответ 1
Один (относительно наивный) подход состоял бы в том, чтобы использовать Expression<Func>
вместо Func
сами, так как у них гораздо более подробная информация, что позволяет анализировать материал. Например.
Expression<Func<int, int, int>> delegate1 = (a, b) => a + b;
Expression<Func<int, int, int>> delegate2 = (a, b) => a + b;
Expression<Func<int, int, int>> delegate3 = (a, b) => a - b;
var id1 = id(delegate1);
var id2 = id(delegate2);
var id3 = id(delegate3);
Debug.Assert(id1 == id2);
Debug.Assert(id1 != id3);
где id
столь же тривиально, как:
public static string id(Expression<Func<int, int, int>> expression)
{
return expression.ToString();
}
проходит тесты.
Обратите внимание, что это не полное решение, и у него много и много проблем. Если вам нужно всестороннее сравнение, вам нужно будет учитывать всю природу выражения, включая (но не ограничиваясь) типы, входящие и выходящие из выражения, вызовы методов, закрытие доступа и т.д. И т.д.
Я не думаю, что это разрешимо вообще в общем случае, но обычно это может быть ограничено некоторым более специализированным случаем, который может быть разрешен.
Ответ 2
Вам нужно работать на уровне Expression
здесь. Компилятор С# не гарантирует, что идентичные lambdas получат один и тот же объект-делегат. Эта оптимизация не выполняется прямо сейчас, но есть проблема GitHub. Даже если он будет выполнен, он будет работать на одной сборке одновременно, что может быть недостаточно для вас. Если делегат фиксирует значения закрытия, это никогда не будет работать.
Я как-то сделал это для того, чтобы автоматически скомпилировать запросы SQLQLQ 2 SQL с учетом запроса. Нетрудно сравнить деревья выражений. ToString
не является полной верностью и медленнее. Вам нужно будет написать свой собственный класс сравнения. Я думаю, что там код для этого в Интернете в качестве отправной точки.
В качестве примера, когда вы сравниваете константные выражения, вы не можете просто сделать ReferenceEquals(val1, val2)
. Вы должны на самом деле особым случаем многих типов, таких как примитивные типы в штучной упаковке. Они могут иметь одинаковое значение, но в коробке с разными идентификаторами объектов.
Вам также понадобится написать функцию хеш-кода, потому что вы, вероятно, захотите просмотреть результаты кэширования с использованием хеш-таблицы.
Вы также получите проблемы с GC, потому что деревья выражений могут удерживаться на произвольных объектах. В принципе, замыкание может случайным образом удерживать локальные переменные неожиданным образом. Так что я сделал, чтобы санировать деревья, прежде чем их кешировать. Я удалил из них все константы, которые не были безопасными.
Можно ли создать идентификатор делегата, чтобы отличить его от другого делегата?
Да, но он включает в себя ручное сравнение и хэширование дерева выражений.
Ответ 3
Здесь нужно использовать Dictionary<T1, T2>
для хранения идентификатора строки и наших делегатов (строка Key содержит отсортированное выражение, объединенное со значением из некоторого вызова вызова [это генерирует UID какого-то типа]), а значение - наш делегат Func. Выражение компилируется только в первый раз, когда мы преобразуем наше выражение, преобразованное в строку в ID, в _delegatesMapping
:
public class Funker
{
private static Dictionary<string, string> _delegatesMapping;
private static Dictionary<string, Func<int, int, int>> _delegates;
public static Funker Instance { get; private set; }
static Funker()
{
_delegatesMapping = new Dictionary<string, string>();
_delegates = new Dictionary<string, Func<int, int, int>>();
Instance = new Funker();
}
private Funker() { }
public Func<int, int, int> this[Expression<Func<int, int, int>> del]
{
get
{
string expStr = del.ToString();
var parameters = del.Parameters;
for (int i = 0; i < parameters.Count; i++)
expStr = expStr.Replace(parameters[i].Name, String.Concat('_' + i));
Func<int, int, int> _Del = null;
if (!_delegatesMapping.ContainsKey(expStr))
{
_Del = del.Compile();
_delegatesMapping.Add(expStr, new String(expStr.OrderBy(ch => ch).ToArray()) + "|" + _Del(55, 77));
}
if (!_delegates.ContainsKey(_delegatesMapping[expStr])) _delegates.Add(_delegatesMapping[expStr], _Del ?? del.Compile());
return _delegates[_delegatesMapping[expStr]];
}
}
}
Нам нужен этот _delegatesMapping
для хранения наших записей UID в формате Expression string → UID. Это также позволяет нам выполнять быстрый (почти n(1)
) поиск, а не вычислять UID каждый раз, когда нам это нужно.
Итак, вся операция выглядит так:
Выражение → textExp → новая запись в _delegatesMapping (textExp → UID)
новая запись в _delegates[UID]
И при обращении к нему все обратное. Сначала мы получаем UID, чем делегат:
Выражение → textExp → _delegates [_delegatesMapping [textExp]] (возвращает делегат, если он существует в словаре).
Пример использования:
class Program
{
static void Main(string[] args)
{
var funker = Funker.Instance;
var del1 = funker[(a, b) => a + 71 + 12 + b];
var del2 = funker[(hello, world) => 71 + hello + world + 12];
var del3 = funker[(a, c) => a + 17 + 21 + c];
Debug.Assert(del1 == del2);
Debug.Assert(del1 == del3);
}
}
Вывод:
true
false
P.S.
Но поскольку SWeko уже написал в своем ответе:
Обратите внимание, что это не полное решение и имеет много и много вопросы. Если вам нужно всестороннее сравнение, вам нужно будет с учетом полного характера выражения, включая (но не слишком ограниченные) типы, входящие и выходящие из выражения, вызовы методов, закрытие доступа и т.д. и т.д.