实现接口函数而不进行装箱的结构体

huangapple go评论76阅读模式
英文:

Struct implementing interface function without boxing

问题

背景

我想允许一个类实例在避免装箱的情况下注册回调函数到事件系统。

一些背景信息:我有一个自制的事件系统,结构如下:

class EventSystem
{
    // 用事件注册一个结构实例
    // 当事件触发时,它调用 run() 方法并传递一些信息
    public void Register(ICallback callbackStruct, Event e);
}

public enum Event
{
    SOME_EVENT,
    SOME_OTHER_EVENT
}

interface ICallback
{
   void Run(string data);
}

这的一个示例实现如下:

class Example
{
    private Behaviour_A a = new Behaviour_A();
    private Behaviour_B b = new Behaviour_B();

    public Example()
    {
        eventSystem.Register(a, SOME_EVENT);
        eventSystem.Register(b, SOME_OTHER_EVENT);
    }

    struct Behaviour_A : ICallback
    {
        void Run(string data)
        {
            // 一些行为
        }
    }

    struct Behaviour_B : ICallback
    {
        void Run(string data)
        {
            // 一些其他行为
        }
    }
}

优点

我喜欢这个解决方案,因为它使用组合而不是继承(还支持在一个实例中为同一事件支持多个回调行为),而且我更愿意使用结构体而不是匿名函数,因为它们是有状态的,可以重复使用不同的参数以及其他一些原因。

缺点

然而,我不喜欢这个解决方案,因为每次传递结构体时都需要装箱,我尽量避免堆分配。

问题

在避免装箱的情况下,是否有办法实现像示例中所示的结构体的自定义行为?

英文:

Background

I want to allow a class instance to register callbacks with an event system while avoiding boxing.

A bit of background: I have a home-grown Event System structured as shown below:

class EventSystem
{
    // registers a struct instance with an event
    // when the event triggers, it calls the run() method and passes in some info
    public void Register(ICallback callbackStruct, Event e);
}

public enum Event
{
    SOME_EVENT,
    SOME_OTHER_EVENT
}

interface ICallback
{
   void Run(string data);
}

An example implementation of this is:

class Example
{
    private Behaviour_A a = new Behaviour_A();
    private Behaviour_B b = new Behaviour_B();

    public Example()
    {
        eventSystem.Register(a, SOME_EVENT);
        eventSystem.Register(b, SOME_OTHER_EVENT);
    }

    struct Behaviour_A : ICallback
    {
        void Run(string data)
        {
            // some behvaiour
        }
    }

    struct Behaviour_B : ICallback
    {
        void Run(string data)
        {
            // some other behvaiour
        }
    }

}

Pros

I like this solution because it makes use of composition rather than inheritance (and also supports multiple callback behaviours for the same event in one instance), and I'd prefer to use structs over anonymous functions since they are stateful and might be re-used with different parameters and a few other reasons.

Cons

However, I dislike this solution because it requires boxing each time the struct is passed around and I'm trying to avoid heap allocations as much as possible.

Question

Is there any way I can implement custom behaviouron a struct as shown while avoiding boxing?

答案1

得分: 1

避免在使用实现接口的结构体时发生装箱的关键是使用接口约束的泛型。

如果有一个接受类型为 T 的参数的函数,其中 T 受限于 IEquatable<T>,那么.NET运行时将为每个传递给它的不同结构类型生成一个单独的机器代码版本的该函数,在这些机器代码版本的函数中将不需要装箱。

英文:

The key to avoiding boxing when using structs that implement interfaces is to use interface-constrained generics.

If one has a function that receives an argument of type T, where T is constrained to IEquatable<T>, then the .NET runtime will generate a separate machine code version of that function for every distinct structure type that is passed to it, and within those machine-code versions of the function no boxing will be required.

答案2

得分: 0

这只是一个建议,而不是一个答案。解耦数据行为,这样设计更清晰。确保注册参数是一个类。这种方式可以处理自己的装箱,也许使用对象池或其他方法。这种方式可以处理不必要的垃圾回收。

public class EventSystem
{
    // 确保这里的T是一个类,而不是结构体。
    public void Register<T>(T callbackStruct, Event e) where T : class, ICallback
    {
    }
}

public enum Event
{
    SOME_EVENT,
    SOME_OTHER_EVENT
}

public interface ICallback
{
    void Run(string data);
}

class Example
{
    private Behaviour_A a = new Behaviour_A();
    private Behaviour_B b = new Behaviour_B();

    public Example()
    {
        EventSystem eventSystem = new EventSystem();

        staticAObj.PutData(a);
        eventSystem.Register(staticAObj, Event.SOME_EVENT);
        eventSystem.Register(b, Event.SOME_OTHER_EVENT);
    }

    struct A
    {
    }
    struct B
    {
    }

    class Behaviour_A : ICallback
    {
        A a;
        public void Run(string data)
        {
            // 一些行为
        }
    }

    class Behaviour_B : ICallback
    {
        B b;
        public void Run(string data)
        {
            // 一些其他行为
        }
    }
}
英文:

It's not a answer but just a suggestion . Decoupling Data and Behaviour , it's more clean design. and make sure Register param is a class. this way is good to handle boxing by you self. maybe use object pool or something. This way can handle unnecessary GC.

public class EventSystem
{
    // here make sure T is a class , not struct.
    public void Register&lt;T&gt;(T callbackStruct, Event e) where T : class , ICallback
    {
    }
}

public enum Event
{
    SOME_EVENT,
    SOME_OTHER_EVENT
}

public interface ICallback
{
    void Run(string data);
}

class Example
{
    private Behaviour_A a = new Behaviour_A();
    private Behaviour_B b = new Behaviour_B();

    public Example()
    {
        EventSystem eventSystem = new EventSystem();

        staticAObj.PutData(a);
        eventSystem.Register(staticAObj, Event.SOME_EVENT);
        eventSystem.Register(b, Event.SOME_OTHER_EVENT);
    }

    struct A
    {
    }
    struct B
    {
    }

    class Behaviour_A : ICallback
    {
        A a;
        public void Run(string data)
        {
            // some behvaiour
        }
    }

    class Behaviour_B : ICallback
    {
        B b;
        public void Run(string data)
        {
            // some other behvaiour
        }
    }

}

答案3

得分: 0

Unless I missed something obvious, your registration method should just add the ref modifier:

public void Register(ref ICallback callbackStruct, Event e);

This guarantees your structure will not be copied on the stack because only the reference will be copied, and your original struct can be used through that. The heap remains intact.

You will have to modify the callsite:

eventSystem.Register(ref a, SOME_EVENT);
eventSystem.Register(ref b, SOME_OTHER_EVENT);

And, based on Guru's comments, this might be a solution: further modify the signature to

void Register<T>(ref T callbackStruct, Event e) where T : ICallback;

or

void Register<T>(ref T callbackStruct, Event e) where T : struct, ICallback;

to remove the box, but you're possibly adding brittleness to this code.

For your EventSystem to register this it would need to be either non-abstract and provide the method below, or it would need a subclass that implements

public void Register<T>(ref T callbackStruct, Event e) where T : struct, ICallback
{}
英文:

Unless I missed something obvious, your registration method should just add the ref modifier:

public void Register(ref ICallback callbackStruct, Event e);

This guarantees your structure will not be copied on the stack because only the reference will be copied, and your original struct can be used through that. The heap remains intact.

You will have to modify the callsite:

eventSystem.Register(ref a, SOME_EVENT);
eventSystem.Register(ref b, SOME_OTHER_EVENT);

And, based on Guru's comments, this might be a solution: further modify the signature to

void Register&lt;T&gt;(ref T callbackStruct, Event e) where T : ICallback;

or

void Register&lt;T&gt;(ref T callbackStruct, Event e) where T : struct, ICallback;

to remove the box, but you're possibly adding brittleness to this code.

For yourEventSystem to register this it would need to be either non-abstract and provide the method below, or it would need a subclass that implements

public void Register&lt;T&gt;(ref T callbackStruct, Event e) where T : struct, ICallback
{}

huangapple
  • 本文由 发表于 2023年2月10日 07:44:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75405593.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定