C
C#14mo ago
RumTery

Delegate caching. Is it safe to use or some dirty hack to avoid? (code below)

Here's some delegate caching into struct...
using System;

public class InstanceHandler<T> where T : InstanceHandler<T>
{
public static T _instance { get; private set; }
public InstanceHandler() => _instance = (T)this;

public readonly struct valuedelegate
{
private readonly Action _instAction;
public valuedelegate(Action<T> action)
{
T caller = _instance;
_instAction = () => action.Invoke(caller);
}
public void Invoke()=> _instAction();
public static implicit operator Action(valuedelegate d) => d._instAction;
}
public readonly struct valuedelegate<N>
{
private readonly Action<N> _instAction;
public valuedelegate(Action<T, N> action)
{
T caller = _instance;
_instAction = x => action.Invoke(caller, x);
}
public void Invoke(N val)=> _instAction(val);
public static implicit operator Action<N>(valuedelegate<N> v) => v._instAction;
}
}

abstract class example : InstanceHandler<example>
{
public void Do() { }
public void Do(int l) { }
methods m;
public class methods
{
public valuedelegate a = new valuedelegate((x) => x.Do());
public valuedelegate<int> b = new valuedelegate<int>((x, z) => x.Do(z));
}

public example():base()
{
m = new methods();
}

public void DoElse()
{
m.a.Invoke();
m.b.Invoke(30);
}

}
using System;

public class InstanceHandler<T> where T : InstanceHandler<T>
{
public static T _instance { get; private set; }
public InstanceHandler() => _instance = (T)this;

public readonly struct valuedelegate
{
private readonly Action _instAction;
public valuedelegate(Action<T> action)
{
T caller = _instance;
_instAction = () => action.Invoke(caller);
}
public void Invoke()=> _instAction();
public static implicit operator Action(valuedelegate d) => d._instAction;
}
public readonly struct valuedelegate<N>
{
private readonly Action<N> _instAction;
public valuedelegate(Action<T, N> action)
{
T caller = _instance;
_instAction = x => action.Invoke(caller, x);
}
public void Invoke(N val)=> _instAction(val);
public static implicit operator Action<N>(valuedelegate<N> v) => v._instAction;
}
}

abstract class example : InstanceHandler<example>
{
public void Do() { }
public void Do(int l) { }
methods m;
public class methods
{
public valuedelegate a = new valuedelegate((x) => x.Do());
public valuedelegate<int> b = new valuedelegate<int>((x, z) => x.Do(z));
}

public example():base()
{
m = new methods();
}

public void DoElse()
{
m.a.Invoke();
m.b.Invoke(30);
}

}
19 Replies
JakenVeina
JakenVeina14mo ago
what on earth? is this supposed to somehow.... what? okay, so you're trying to pre-allocate a delegate for each method on example which consumers of example can use what exactly is wrong with...
public static Example
{
public Example()
{
Methods = new Methods()
{
Do = Do,
DoForInt = DoForInt
};
}

public Methods Methods { get; }
public void Do() {}
public void DoforInt(int i) {}

public class Methods
{
public required readonly Action Do;
public required readonly Action<int> DoForInt;
}
}
public static Example
{
public Example()
{
Methods = new Methods()
{
Do = Do,
DoForInt = DoForInt
};
}

public Methods Methods { get; }
public void Do() {}
public void DoforInt(int i) {}

public class Methods
{
public required readonly Action Do;
public required readonly Action<int> DoForInt;
}
}
what does the 7 layers of indirection accomplish? or better yet
public class Example
{
public Example()
{
Do = () => {};
DoForInt = _ => {};
}

public required readonly Action Do;
public required readonly Action<int> DoForInt;
}
public class Example
{
public Example()
{
Do = () => {};
DoForInt = _ => {};
}

public required readonly Action Do;
public required readonly Action<int> DoForInt;
}
on a fundamental note, I'm not convinced this would even optimize anything do you have any actual data to back up a need for something like this?
RumTery
RumTeryOP14mo ago
It should cache delegates, which called somewhere... More real world code...
public abstract class BaseView<T, M> : BaseView, IBaseView where T : BaseView<T, M> where M: new()
{
private static T _instance;
protected readonly M _methods;

public readonly struct action<N>
{
private readonly Action<N> _instAction;
public action(Action<T, N> action)
{
T caller = _instance;
_instAction = x => action.Invoke(caller, x);
}
public static implicit operator Action<N>(action<N> v) => v._instAction;
public void Invoke(N value) => _instAction.Invoke(value);
}

public BaseView()
{
_instance = (T)this;
_methods = new M();
_instance = null;
}
}


public sealed class ProjectileView : BaseView<ProjectileView, ProjectileView.Methods>
{
public struct Context
{
public IReactiveField<vec3> Position;
public IGameEvent OnTargetCollided;
public ITarget Target;
}
public class Methods
{
public readonly action<vec3> pos;
public Methods()
{
pos = new action<vec3>((ins, pos) => ins.transform.position = pos);
}
}

public void SetContext(Context context)
{
_context = context;
_methods.pos.Invoke(_context.Position.Value);
_context.Position.AddListener(_methods.pos);
}

public override void OnAfterDisable()
{
base.OnAfterDisable();
_context.Position.RemoveListener(_methods.pos);
}
}
public abstract class BaseView<T, M> : BaseView, IBaseView where T : BaseView<T, M> where M: new()
{
private static T _instance;
protected readonly M _methods;

public readonly struct action<N>
{
private readonly Action<N> _instAction;
public action(Action<T, N> action)
{
T caller = _instance;
_instAction = x => action.Invoke(caller, x);
}
public static implicit operator Action<N>(action<N> v) => v._instAction;
public void Invoke(N value) => _instAction.Invoke(value);
}

public BaseView()
{
_instance = (T)this;
_methods = new M();
_instance = null;
}
}


public sealed class ProjectileView : BaseView<ProjectileView, ProjectileView.Methods>
{
public struct Context
{
public IReactiveField<vec3> Position;
public IGameEvent OnTargetCollided;
public ITarget Target;
}
public class Methods
{
public readonly action<vec3> pos;
public Methods()
{
pos = new action<vec3>((ins, pos) => ins.transform.position = pos);
}
}

public void SetContext(Context context)
{
_context = context;
_methods.pos.Invoke(_context.Position.Value);
_context.Position.AddListener(_methods.pos);
}

public override void OnAfterDisable()
{
base.OnAfterDisable();
_context.Position.RemoveListener(_methods.pos);
}
}
JakenVeina
JakenVeina14mo ago
okay, but again what purpose does any of the indirection serve the ENTIRETY of the action<n> struct and the M type parameter on the base class accomplishes one thing: you get to move _methods = new M() from the subclass into the base class it doesn't save you from writing lines of code to define WHAT the methods are, on each base class it doesn't allow someone who only has the base class, without knowing the subtype to use any of the methods you're trying to enforce a coding paradigm with the compiler don't do that
RumTery
RumTeryOP14mo ago
As defaul field initializers can't reference anything except static, so all should be done in class initializer To keep inherited objectects initializer clear i want to make fields somehow Also need to cache Actions to prevent allocations inside OnAfterDisable.
JakenVeina
JakenVeina14mo ago
especially since this structure doesn't actually really FORCE the designer to do what you want
public class Example
{
public Example()
{
Do = () => {};
DoForInt = _ => {};
}

public required readonly Action Do;
public required readonly Action<int> DoForInt;
}
public class Example
{
public Example()
{
Do = () => {};
DoForInt = _ => {};
}

public required readonly Action Do;
public required readonly Action<int> DoForInt;
}
this accomplishes all requirements except "keep inherited objectects initializer clear" which your solution doesn't accomplish either not to mention even if this WAS sensible you can still get rid of the entire action<N> struct with... ah, nevermind on that last bit
RumTery
RumTeryOP14mo ago
also required not supported by my language version
JakenVeina
JakenVeina14mo ago
?? what're you using some long-outdated version of Unity?
RumTery
RumTeryOP14mo ago
Unity's last supported C# is 9.0. required is C# 11.0 feature
JakenVeina
JakenVeina14mo ago
okay so don't use that oh, okay, that's what you meant "required" meaning the keyword really, required in that context doesn't accomplish anything anyway
RumTery
RumTeryOP14mo ago
but it's too new
JakenVeina
JakenVeina14mo ago
what is?
RumTery
RumTeryOP14mo ago
"required" keyword. Too new for usage in Unity. They're still can't update to .net 6 or above
JakenVeina
JakenVeina14mo ago
yes, I know cause you just said that to which I responded "don't use that" then I elaborated that it doesn't accomplish anything in this scenario anyway, so I shouldn't have put it there
RumTery
RumTeryOP14mo ago
Problem could be solved with propety with baking field, or using constructor. First requires adding a baking field and null check every time. Second adds more constructor-related code (but there's already enough code as SetContext) and divides methods and it's creation onto two code parts which is not very convenient
JakenVeina
JakenVeina14mo ago
you want #2 put initialization code in your constructor hell, move everything in SetContext there if possible
RumTery
RumTeryOP14mo ago
Context could be changed.
JakenVeina
JakenVeina14mo ago
then keep SetContext() and add it as a constructor parameter if it's necessary for a context to be provided before the object can function, it belongs in the constructor if it can be changed later, that's a separate requirement
RumTery
RumTeryOP14mo ago
It can be changed. The whole this hack is supposed to cache delegates and reuse them when context changed and pooling is needed.
JakenVeina
JakenVeina14mo ago
yes, I understand you do not need a monstrous base class to accomplish this
Want results from more Discord servers?
Add your server