如何将委托事件从 C# 转换为需要 2 个输入和 1 个输出参数的 Kotlin?

问题描述

我正在将一个库从 c# 移植到 kotlin,并且在一个类中我有一个委托事件,它有 2 个输入参数和一个输出参数

public delegate bool MyDelegate(int para1,int param2)

public event MyDelegate IsFinished


这个事件也在定义它的类的函数调用

if(IsFinished){
//do somethings
}

所有这些,在 kotlin 中,我该如何翻译?

实际上,我试图创建一个模拟事件的类

class EventTwoReturnBoolean<T,U> {

    private val observers = mutableSetof<(T,U) -> Boolean>()

    operator fun plusAssign(observer: (T,U) -> Boolean) {
        observers.add(observer)
    }

    operator fun minusAssign(observer: (T,U) -> Boolean) {
        observers.remove(observer)
    }

    operator fun invoke(value: T,value2: U) : Boolean {
        var bool : Boolean = true
        for (observer in observers){
            bool = observer(value,value2)
        }
            return bool
    }

}

这是一个有效的解决方案吗?

解决方法

C# 内置了对 EventHandler 模式的支持。这是 Observer pattern 的变体,它允许主题(EventHandler 术语中的publisher)具有多个观察状态(EventHandler 术语中的事件)。此外,它还显式地将观察者(EventHandler 术语中的subscriber事件处理程序 方法的参数提取到单独的实体中,同时将引用传递给发布者

在 C# 中,它在语言(eventdelegate 关键字)和 stdlib 级别(EventHandlerEventArgs 类)上实现,而 Kotlin 不提供此功能。但是,可以在 Kotlin 中重新创建完全相同的 API(通过 +=/-= 订阅/取消订阅,通过 () 引发事件)。

实际上,您正在尝试实现 EventHandler 模式的一些变体(其中委托返回一个值)。我将展示如何在 Kotlin 中实现经典的 EventHandler 模式,它可能会帮助你实现你的。

首先,我们需要定义相同的基本类:

interface EventArgs

typealias Observer<T> = (sender: Any,eventArgs: T) -> Unit

//In C# EventHandler subscription/unsubscription is thread-safe,so we need to keep it this way in Kotlin too
//Variant 1 - pure Kotlin,thread-safety is lock-based
class EventHandler<T : EventArgs> {
    private val subscribers = mutableSetOf<Observer<T>>()

    operator fun plusAssign(subscriber: Observer<T>) {
        synchronized(subscribers) { subscribers.add(subscriber) }
    }

    operator fun minusAssign(observer: Observer<T>) {
        synchronized(subscribers) { subscribers.remove(observer) }
    }

    operator fun invoke(publisher: Any,args: T) {
        subscribers.forEach { it.invoke(publisher,args) }
    }
}

//Variant 2 - JVM-specific,but more multihread-performant
class EventHandler<T : EventArgs> {
    private val subscribers = CopyOnWriteArraySet<Observer<T>>()

    operator fun plusAssign(subscriber: Observer<T>) {
        subscribers.add(subscriber)
    }

    operator fun minusAssign(observer: Observer<T>) {
        subscribers.remove(observer)
    }

    operator fun invoke(publisher: Any,args) }
    }
}

这里是微软官方的“基于 EventHandler 模式发布事件”编程指南的基本 example 直接翻译成 Kotlin:

// Define a class to hold custom event info
class CustomEventArgs(var message: String) : EventArgs

// Class that publishes an event
open class Publisher {
    // Declare the event using EventHandler<T>
    val raiseCustomEvent = EventHandler<CustomEventArgs>()

    fun doSomething() {
        // Write some code that does something useful here
        // then raise the event. You can also raise an event
        // before you execute a block of code.
        onRaiseCustomEvent(CustomEventArgs("Event triggered"))
    }

    // Wrap event invocations inside a protected method
    // to allow derived classes to override the event invocation behavior
    protected fun onRaiseCustomEvent(e: CustomEventArgs) {
        //In this Kotlin implementation raiseCustomEvent is always non-null,even if there are no subscribers,// so all that actions to avoid NPE in C# implementation are redundant here

        // Format the string to send inside the CustomEventArgs parameter
        e.message += " at ${Instant.now()}" // pure Kotlin alternative - `Clock.System.now()` using https://github.com/Kotlin/kotlinx-datetime library
        // Call to raise the event.
        raiseCustomEvent(this,e)
    }
}

//Class that subscribes to an event
class Subscriber(val id: String,pub: Publisher) {
    init {
        // Subscribe to the event
        pub.raiseCustomEvent += ::handleCustomEvent
    }

    // Define what actions to take when the event is raised.
    fun handleCustomEvent(sender: Any,e: CustomEventArgs) {
        println("$id received this message: ${e.message}");
    }
}

fun main() {
    val pub = Publisher()
    val sub1 = Subscriber("sub1",pub)
    val sub2 = Subscriber("sub2",pub)

    // Call the method that raises the event
    pub.doSomething()

    // Keep the console window open
    println("Press any key to continue...")
    readLine()
}

这里的主要缺陷是 invokeEventHandler 方法可以在 Kotlin 中从任何地方调用(在 C# 中它只能从声明它的类中调用):

fun main() {
    val pub = Publisher()
    pub.raiseCustomEvent(pub,CustomEventArgs("Oops!"))
}

为了履行这个契约,EventHandler 声明需要改变(invoke 方法需要变成 protected 和类本身 - abstract,因为直接实例化它不会现在有意义):

abstract class EventHandler<T : EventArgs> {
    private val subscribers = mutableSetOf<Observer<T>>()

    operator fun plusAssign(subscriber: Observer<T>) {
        synchronized(subscribers) { subscribers.add(subscriber) }
    }

    operator fun minusAssign(observer: Observer<T>) {
        synchronized(subscribers) { subscribers.remove(observer) }
    }

    protected operator fun invoke(publisher: Any,args) }
    }
}

事件声明将成为样板:

open class Publisher {
    // Declare the event using EventHandler<T>
//- val raiseCustomEvent = EventHandler<CustomEventArgs>()

    //Declare backing property,extending EventHandler<T> and thus having access to its protected members
    private val _raiseCustomEvent = object : EventHandler<CustomEventArgs>() {
        operator fun invoke(args: CustomEventArgs) = super.invoke(this@Publisher,args)
    }

    // Declare the event referencing backing property
    val raiseCustomEvent get() = _raiseCustomEvent

    //Define public invoke operator extension in the scope of the Publisher class
    //Can't access raiseCustomEvent directly - it will lead to recursive problem (StackOverflowError in runtime)
    // that's why we need backing property
    operator fun EventHandler<CustomEventArgs>.invoke(args: CustomEventArgs) = _raiseCustomEvent.invoke(args)
}

虽然事件引发会变得更清晰:

//- raiseCustomEvent(this,e)
    raiseCustomEvent(e)

Now 事件只能在 Publisher 的范围内引发:

fun main() {
    val pub = Publisher()
    pub.raiseCustomEvent(pub,CustomEventArgs("Oops!")) //Won't compile
    pub.raiseCustomEvent(CustomEventArgs("Oops!")) //Won't compile too
}

也许,可以通过基于注释处理的代码生成来减少样板,但这超出了我的回答范围