Вы наверняка сталкивались с такой проблемой:
void Method<T>(System.Action<T> callback) {...}
То есть мы хотим вызвать метод, который вернет нам колбэк в какой-то момент. Например, что действие завершено. Все вроде хорошо, но если мы начнем это использовать, то сталкнемся с другой проблемой:
Method<MyObj>(static (obj) => {
// И вот тут нам явно не хватает данных при static
});
Отсюда 2 выхода: либо мы используем аллокации при замыкании, либо реализуем некий второй параметр, в который мы сможем положить любые данные:
void Method<T1, T2>(T1 t1, System.Action<T1, T2> callback) {...}
С точки зрения метода будем считать, что T1 - это замыкание, которое мы передаем в метод:
Method<int, MyObj>(123, static (closure, obj) => { ... });
Вроде уже неплохо, но что если у нас вместо простого int будет что-то сложнее, например, кортеж ValueTuple:
Method<ValueTuple<int, int>, MyObj>((123, 234), static (closure, obj) => { ... });
Выходит уже неприятно, а если представить, что это мы захотим передать несколько параметров разных своих типов, то будет прям совсем нечитаемо.
И тут тоже есть элегантное решение:
struct ClosureAPI<TClosure> {
public TClosure data;
public MyContainer container;
public Method<T>(System.Action<T, TClosure> callback) {
this.container.Method(this.data, callback);
}
}
ClosureAPI<TClosure> Closure<TClosure>(TClosure data) {
return new ClosureAPI<TClosure>() {
data = data,
container = this,
};
}
void Method<T1, T2>(T1 t1, System.Action<T1, T2> callback) {...}
Таким образом снаружи это будет выглядеть как:
container.Closure((123, 234)).Method<MyObj>(static (data, obj) => { ... });
Вот таким образом мы избавляемся от необходимости указывать дженерик тип, который используем.
Естественно, если вы передаете все инстансы в метод, то вам не нужно указывать типы, но если (как в примере выше) у вас тип передаете только как дженерик, то такой подход избавит от необходимости указывать дженерик замыкания.
#zeroalloc #architecture #code