c# - 如何将需要 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# 具有对 EventHandler 模式的内置支持。这是观察者模式的一种变体,它允许主体(EventHandler 术语中的发布者)具有多个观察状态( EventHandler 术语中的事件)。此外,它显式地将观察者( EventHandler 术语中的订阅者)事件处理程序方法的参数提取到单独的实体中,同时传递对发布者的引用。
在 C# 中,它在语言(event
和delegate
关键字)和 stdlib 级别(EventHandler
和EventArgs
类)上实现,而 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
}
可能,可以通过基于注释处理的代码生成来减少样板文件,但这超出了我的回答范围
推荐阅读
- ios - Any, Dark 用于颜色,Any, Light, Dark 用于图像,为什么?
- reactjs - 反应 svg 图片
- vue.js - 通过外键搜索数据并将其显示在 bootstrap-vue 表中
- r - R中两列之间的部分字符串匹配
- svg - 模板和 SVG 属性 xlink-href
- python - 正则表达式 - 在字符串中查找日期模式
- ruby - 如何使用 Rspec 深入匹配哈希并获得合理的匹配错误?
- getstream-io - GetStream 聊天 API 开启线程
- java - javafx 或 fxml 中有什么方法可以验证 TextField 字符的长度吗?
- laravel - 无法使用 Faker 映像写入目录