非委托类型的事件

5

我已经实现了一个类,它看起来像这个接口:

[ImmutableObject(true)]
public interface ICustomEvent
{
    void Invoke(object sender, EventArgs e);

    ICustomEvent Combine(EventHandler handler);
    ICustomEvent Remove(EventHandler handler);

    ICustomEvent Combine(ICustomEvent other);
    ICustomEvent Remove(ICustomEvent other);
}

这个CustomEvent类的工作方式与MulticastDelegate类似。它可以被调用,可以与另一个CustomEvent组合。而且一个CustomEvent可以从另一个CustomEvent中删除。

现在,我想声明一个像这样的类:

class EventProvider
{
    public event CustomEvent MyEvent;

    private void OnMyEvent()
    {
        var myEvent = this.MyEvent;
        if (myEvent != null) myEvent.Invoke(this, EventArgs.Empty);
    }
}

很遗憾,这段代码无法编译。出现了编译器错误CS0066:
“EventProvider.MyEvent”:事件必须是委托类型。
基本上,我需要的是一个具有添加和删除访问器而不是获取和设置访问器的属性。我认为唯一的方法是使用“event”关键字。我知道一个明显的替代方案是声明两个方法来执行添加和删除,但我也想避免这种情况。
是否有人知道有没有好的解决方法?我想知道是否有任何方法可以欺骗编译器接受非委托类型作为事件。也许是自定义属性。
顺便说一下,有人在experts-exchange.com上提出了类似的问题。由于该网站不是免费的,因此我无法查看回复。这是主题:http://www.experts-exchange.com/Programming/Languages/C_Sharp/Q_21697455.html

从他在EE链接中的事件名称来看,他似乎正在尝试进行COM事件。你应该避免这种情况。 - John Saunders
1
我真的看不出问题在哪里。常规事件有什么问题吗? - Fredrik Mörk
常规事件没有问题,问题在于常规委托。它们会对目标对象保持强引用。我实现了一个类似委托的类,但内部使用WeakReference。如果我只能在事件属性中使用它,那么解决方案就完美了。 这只是其中一种可能性。我知道这听起来像是重复造轮子,但我相当确定不是这样。 - jpbochi
如果你要使用自己的事件式架构,我建议你将其模式化为IObservable而不是委托组合/删除模式。后者会在事件被订阅/取消订阅多次时创建不可避免的歧义,需要事件订阅者保持对发布者的强引用以允许取消订阅,即使在其他情况下不需要强引用,也很难保持必要的取消订阅列表等。前者避免了所有这些问题。 - supercat
4个回答

5

试试这个:

CustomEvent myEvent

public event EventHandler MyEvent {
    add { myEvent = myEvent.Combine(value); }
    remove {myEvent = myEvent.Remove(value); }
}

您可以向其中添加和删除普通的EventHandler委托,并且它将执行add和remove访问器。
编辑: 您可以在此处找到弱事件实现here.
第二次编辑: 或者here.

1st) 感谢您的建议,但它与常规事件完全相同。这不是我要找的; 2nd) 我想从EventProvider类中引发事件。我认为在任何情况下都不应该从外部类引发事件; 3rd) 感谢您提供的链接,但我已经知道它们两个。顺便说一下,我的代码受到了您提到的codeproject文章的强烈启发。 - jpbochi
如果第一个选项不是你想要做的,那么你想要什么?<br>想要能够添加“ICustomEvent”吗? - SLaks
对不起,我误读了你的回答。你是正确的,我想能够添加和删除ICustomEvent,而不仅仅是EventHandler。我修改了问题以使其更清晰。(如果我有足够的声望,我会投票支持这个答案) - jpbochi

2
如果您想能够添加和删除CustomEvent对象到事件中(而不是常规的委托),有两个选项:
1. 从ICustomEvent到EventHandler(或其他委托)进行隐式转换,返回ICustomEvent的实例方法(可能是Invoke),然后使用委托的Target属性在addremove访问器中获取原始的ICustomEvent。
2. 像这样:EDIT
CustomEvent myEvent;
public event EventHandler MyEvent {
    add {
        if (value == null) throw new ArgumentNullException("value");
        var customHandler = value.Target as ICustomEvent;

        if (customHandler != null)
            myEvent = myEvent.Combine(customHandler);
        else
            myEvent = myEvent.Combine(value);   //An ordinary delegate
    }
    remove {
        //Similar code
    }
}

请注意,如果事件处理程序是委托(如果 myEvent 字段为空),您仍然需要弄清楚如何添加第一个处理程序。
创建一个类型为CustomEvent的可写属性,然后重载+-运算符来允许在该属性上使用+=-=编辑:为了防止调用者覆盖事件,您可以在CustomEvent中公开先前的值(我假设它像不可变堆栈一样工作),并在setter中添加。
if (myEvent.Previous != value && value.Previous != myEvent)
    throw new ArgumentException("You cannot reset a CustomEvent", "value");

请注意,当最后一个处理程序被删除时,valuemyEvent.Previous都将变为null

关于可写属性选项:我曾经考虑过它,但是我不喜欢它有一个很好的理由。任何外部代码都可以覆盖当前的CustomEvent。我希望提供与常规事件相同的模式的解决方案:用户代码只能添加/删除它知道的处理程序。不允许调用事件和读取当前处理程序。 - jpbochi
您需要查看谷歌页面的缓存。在谷歌中搜索cache:<任何ExpertsExchange Url>, 或使用谷歌工具栏的查看缓存按钮。 - SLaks
@1st: 一个很好的功能是能够将EventHandler隐式转换为CustomEvent。使用委托类型的事件,我可以有两个选项。增加的价值可以是通用的EventHandler或伪装成EventHandler的CustomEvent。为了区分这两者,我可以检查委托方法是否具有某些自定义属性。我开始喜欢这个解决方案。 - jpbochi
@2nd: 的确,这也可以起作用。现在,我不知道先选择哪个选项。我想我将被迫接受你的答案。 :) - jpbochi
@1st: 你说得对,我不需要属性。我只需要一个更正。空值是可以接受的,但必须被忽略。这就是常规事件的工作原理。因此,如果你将*throw new ArgumentNullException("value");替换为return;*,那么我接受你的答案。非常感谢。 :) - jpbochi
显示剩余3条评论

1

我 tend to agree。如果你需要在添加和删除事件处理程序上执行任务,则使用 addremove 访问器,这是你提到的。 - Noldorin
其中一种可能性是更容易实现透明的弱事件模式。 - jpbochi
也许,但这样你就会有违反你已经了解的.NET事件和弱事件的所有内容的代码,尽管你正在尝试改进它,但这会破坏代码的可读性。 - Sam Harwell

0
为什么不尝试在CustomEvent类中使用"+="和"-="运算符呢? 你不能直接重载"+="和"-="运算符,但它们会被"+"和"-"运算符所评估。 赋值运算符无法被重载,但例如"+="是通过重载"+"而评估的。

http://msdn.microsoft.com/en-us/library/8edha89s(VS.90).aspx

所以,与其拥有类似事件的添加和删除方法,您可以拥有一个可以通过+=和-=运算符组合的字段或属性。此外,它将组合逻辑封装在您自己的CustomEvent类中。
Carlos Loth。

这与SLaks上面提出的第二个选项是一样的。他还提出了一种特殊处理方法,以避免事件被覆盖的可能性。说实话,我更喜欢他的第一个选项。我已经编写了代码并正在进行一些测试。 - jpbochi
我必须同意你的看法,第一个选项更好,因为你实际上可以在自定义弱事件之外使用“常规”事件处理程序。 此外,我没有注意到他关于重载+和-运算符的评论。抱歉。 - CARLOS LOTH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接