C#编程:非常好玩的C# .NET 基础 -- 安全有效引发事件
小标 2018-05-18 来源 : 阅读 1392 评论 0

摘要:有一种C#编程方式叫 Cargo Cult Programming, 中文名: 货物崇拜编程. 维基定义为"其特征为不明就里地仪式性地使用代码或程序架构。货物崇拜编程通常是一个程序员既没理解他要解决的 bug,也没理解表面上的解决方案的典型表现。"希望对大家学习C#编程有所帮助。

最近在网上看到一篇很好的文章, 讨论如何安全有效的引发事件.

也许你不一定要用到下面相同的解决方案, 但是至少你应该知道在引发事件时候需要考虑的问题.

引发事件的问题

引发事件是一个非常容易的事情, 但是的确也有它的误区. 让我们举个例子. 假设我们写个消息接收器, 每当我们收到一个新消息, 我们引发一个包含了新消息的事件 MessageReceived.

安装我们通常的方法,就是:

public class MessageReceivedEventArgs : EventArgs
{
    // 接收到的消息
    public string Message { get; private set; }
    // 架构 ReceivedEventArgs 
    public MessageReceivedEventArgs(string message)
    {
        Message = message;
    }
}

接下来, 我们创建一个非线程安全访问的类UnsafeMessenger来实现这个消息同时通知所有的订阅者

public class UnsafeMessenger
{
    public event EventHandler<MessageReceivedEventArgs> MessageReceived;
 
    // 当收到新消息时调用
    public void OnNewMessage(string message)
    {
        if (MessageReceived != null)
        {
            MessageReceived(this, new MessageReceivedEventArgs(message));
        }
    }
}

注意, 通常OnNewMessage() 是私有的, 但是在这里为了测试的方便,我们将它设为public.

 

大功告成!! 是吗? 事实上, 如果我们是单线程的程序, 这的确已经足够, 但是这是非线程安全访问(thread-safe).

为什么? 想想, 订阅者可以任何时候订阅或者取消订阅. 比如,我们当前有一个订阅者, 那么当接收到一个新消息,执行到这一句时:

if (MessageReceived != null)

肯定会通过, 因为有一个订阅者, 如果这个时候, 这名订阅者执行了取消订阅的命令:

myMessenger.MessageReceived -= MyMessageHandler;

 

那么MessageReceived委托 就为null 了,

 

//已经通过了这个IF语句
if (MessageReceived != null)
{
    //MessageReceived委托 就为null 了, 但是我们将要执行这句
    MessageReceived(this, new MessageReceivedEventArgs(message));
}

这个时候, 就会引发NullReferenceException.

 

方案一: 锁住它, 锁机制

当允许多线程的时候, 我们可以用锁机制来避免一个用户在我们执行事件时订阅或者取消订阅, 或者在用户执行操作时, 不能引发事件.

public class SyncronizedMessenger : IMessenger
{
    // 委托和锁
    private EventHandler<MessageReceivedEventArgs> _messageReceived;
    private readonly object _raiseLock = new object();
    // 订阅/取消订阅的锁机制
    public event EventHandler<MessageReceivedEventArgs> MessageReceived
    {
        add { lock (_raiseLock) { _messageReceived += value; } }
        remove { lock (_raiseLock) { _messageReceived -= value; } }
    }
 
    // 引发事件的锁机制
    public void OnNewMessage(string message)
    {
        lock (_raiseLock)
        {
            if (_messageReceived != null)
            {
                _messageReceived(this, new MessageReceivedEventArgs(message));
            }
        }
    }
}

这样, 如果有人试图订阅或取消订阅时, 必须要等待OnNewMessage事件的完成, 反之亦然.

 

 

方案二: 永不为空, 默认加载一个订阅者

我们面临的主要问题是有可能委托为空. 那么如果事先加载一个委托,会怎么样?

public class EmptySubscriberMessenger : IMessenger
{
    // 立刻给它一个空的订阅者
    public event EventHandler<MessageReceivedEventArgs> MessageReceived = (s, e) => { };
    // 现在根本无需检查是否为 null!
    public void OnNewMessage(string message)
    {
        MessageReceived(this, new MessageReceivedEventArgs(message));
    }
}

方案三: 创建一个本地的委托副本

另外一个简单的方案, 也就是很多人都在使用的, 微软建议的模式: 创建一个本地的委托副本.

public class LocalCopyMessenger : IMessenger
{
    public event EventHandler<MessageReceivedEventArgs> MessageReceived;
 
    // 当我们引发事件时, 做一个副本
    public void OnNewMessage(string message)
    {
        var target = MessageReceived;
        if (target != null)
        {
            target(this, new MessageReceivedEventArgs(message));
        }
    }
}

下面是以上四种方法的效率, 在执行10亿次的重复操作时:

 C#编程:非常好玩的C# .NET 基础 -- 安全有效引发事件

小结

有一种编程方式叫 Cargo Cult Programming, 中文名:  货物崇拜编程. 维基定义为"

其特征为不明就里地仪式性地使用代码或程序架构。货物崇拜编程通常是一个程序员既没理解他要解决的 bug,也没理解表面上的解决方案的典型表现。

这个名词有时也指不熟练的或没经验的程序员从某处拷贝代码到另一处,却不太清楚其代码是如何工作的,或者不清楚在新的地方是否需要这段代码。也可以指不正确或过份的应用设计模式,代码风格或编程方法,却对其原理不明就里。"

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C#.NET频道!


本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程