首页 > 解决方案 > 如何将需要 2 个输入和 1 个输出参数的委托事件从 C# 转换为 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#kotlineventsdelegatesporting

解决方案


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

在 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: T) {
        subscribers.forEach { it.invoke(publisher, args) }
    }
}

以下是微软官方“基于 EventHandler 模式发布事件”编程指南中基本示例的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()
}

这里的主要缺陷是可以在 Kotlin 中从任何地方调用 的invoke方法EventHandler(在 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: T) {
        subscribers.forEach { it.invoke(publisher, 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)
}

虽然事件 raise 会变得更干净一些:

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

现在事件只能在以下范围内引发Publisher

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

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


推荐阅读