【问题标题】:Event Handlers and Interfaces事件处理程序和接口
【发布时间】:2012-05-16 08:13:35
【问题描述】:

我有一个名为 IDataIO 的接口:

public interface IDataIO
{
  event DataReceivedEvent DataReceived;
  //.....more events,methods and properties
}

我也有多个类实现了这个接口,分别是UdpIOTcpIOSerialIO

现在,我有一个IO 类,允许我在不同的输入/输出硬件之间切换。此类的每个实例都有一个CurrentIODevice 属性,它可以是SerialIOUdpIOTcpIO 之一。分配此属性后,我将 1 个或多个处理程序附加到 DataReceivedEvent 以便在收到传入数据时通知我的 GUI,以及需要通知的其他类。

public class IO
{
  IDataIO CurrentIODevice;

  public IO()
  {
    SerialIO serial = new SerialIO();
    TcpIO tcp = new TcpIO();
    UdpIO udp = new UdpIO();
    CurrentIODevice = serial;
  }
}

我还有一个 IOManager 类,它包含多个 IO 对象。

public class IOManager
{
  List<IO> Ports = new List<IO>();
  public IOManager()
  {
    Ports.Add(new IO());
    Ports.Add(new IO());
  }

  Ports[0].CurrentIODevice = serial;
  Ports[0].CurrentIODevice.DataReceivedHandler += MyGuiUpdate;
  Ports[0].CurrentIODevice.DataReceivedHandler += MyDataProcessing;
}

我关心(这不是问题 atm)是我将如何在运行时在不同的 IDataIO 接口之间进行更改。

在运行时执行以下语句的效果是什么:

//i know this is illegal but just to demonstrate
IOManager.Ports[0].CurrentIODevice = tcp; 

事件处理程序是否仍然正常运行(并且正确)?

我是否需要在分配 CurrentIODevice 之前取消分配事件,然后再重新分配处理程序?如果是这种情况,我可以看到这种方法变得非常混乱,所以如果有人有更好的方法来解决这个问题,我会全力以赴:)

【问题讨论】:

    标签: c# .net events interface


    【解决方案1】:

    不,您的处理程序将不起作用,因为它们已附加到旧对象。接口提供...对象的接口,将其视为一种契约,但它们本身并不是不同的对象。

    如果您需要在接口的不同实现之间切换(在运行时)并保持所有处理程序正常工作,您必须对接口本身具有相同的对象引用,类似于strategy pattern(或多或少)。

    例如,您可以在DataIO 对象中实现IDataIO 接口。它将公开一个属性(或一个方法,我认为它的意图更清楚)以在该接口的不同实现(串行、TCP 或其他)之间切换。它将是唯一一个将事件处理程序附加到该接口的对象(当具体实现发生变化时,它将删除处理程序)。该对象的用户将始终看到它,无论它使用的是什么具体实现。

    示例

    这是一个解释这个概念的小例子。通用接口是这样的:

    interface IDataIO
    {
        void Write(byte[] data);
    
        byte[] Read();
    
        event EventHandler DataReceived;
    }
    

    这是IDataIO的具体实现,其他类就直接使用这个类:

    sealed class DataIO : IDataIO
    {
        public void SetChannel(IDataIO concreteChannel)
        {
            if (_concreteChannel != null)
                _concreteChannel.DataReceived -= OnDataReceived;
    
            _concreteChannel = concreteChannel;
            _concreteChannel.DataReceived += OnDataReceived;
        }
    
        public void Write(byte[] data)
        {
            _concreteChannel.Write(data);
        }
    
        public byte[] Read()
        {
            return _concreteChannel.Read();
        }
    
        public event EventHandler DataReceived;
    
        private IDataIO _concreteChannel;
    
        private void OnDataReceived(object sender, EventArgs e)
        {
            EventHandler dataReceived = DataReceived;
            if (dataReceived != null)
                dataReceived(this, e);
        }
    }
    

    最后是一些测试代码:

    class Test
    {
        public Test()
        {
            _channel = new TcpIO();
    
            _channel.DataReceived += OnDataReceived;
        }
    
        public void SetChannel(IDataIO channel)
        {
            _channel.SetChannel(channel);
    
            // Nothing will change for this "user" of DataIO
            // but now the channel used for transport will be
            // the one defined here
        }
    
        private void OnDataReceived(object sender, EventArgs e)
        {
            // You can use this
            byte[] data = ((IDataIO)sender).Read();
    
            // Or this, the sender is always the concrete
            // implementation that abstracts the strategy in use
            data = _channel.Read();
        }
    
        private DataIO _channel;
    }
    

    【讨论】:

    • +1 谢谢,我原来的 CurrentIODevice 确实未分配并在 setter 方法中重新分配了事件处理程序,但我同意一种方法可能更有意义。如果在更改接口对象时,我存储对所有事件处理程序的引用,取消分配它们,然后将存储的事件处理程序重新分配给新对象 - 我假设这会起作用?
    • @Simon 听起来很容易出错,所以没有必要。 ;) DataIO 实现 IDataIO;用户将附加到 DataIO 公开的事件;只有 DataIO 附加到具体实现暴露的事件;当 DataIO 处理来自实现的事件时,它只是将事件转发给它的侦听器(它引发它的事件)。当您更改策略时,DataIO 会将其事件处理程序与旧实现分离,将附加到新实现,仅此而已。
    • @Simon 用一个小例子更新了答案
    【解决方案2】:

    显然你应该考虑strategy pattern。我先贴代码,后面再解释:

    public interface IDataIO
    {
        event DataReceivedEvent DataReceived;
    
        //this the new added method that each IO type should implement.
        void SetStrategy();
    }
    
    public class SerialIO : IDataIO
    {
        public void SetStrategy()
        {
            //put the code that related to the Serial IO.
            this.DataReceivedHandler += MyGuiUpdate;
            this.DataReceivedHandler += MyDataProcessing;
        }
    }
    
    public class TcpIO : IDataIO
    {
        public void SetStrategy()
        {
            //put the code that related to the Tcp IO.
            //I will not implement it because it is a demo.
        }
    }
    
    public class UdpIO : IDataIO
    {
        public void SetStrategy()
        {
            //put the code that related to the Udp IO.
            //I will not implement it because it is a demo.
        }
    }
    
    public class IO
    {
        IDataIO port = new IDataIO();
    
        public void SetIOType(IDataIO ioType)
        {
            this.port = ioType;
    
            port.SetStrategy();
        }
    
    }
    
    public class IOManager
    {
        List<IO> ports = new List<IO>();
    
        SerialIO serial = new SerialIO();
        TcpIO tcp = new TcpIO();
    
        ports[0].SetIOType(serial);
        ports[1].SetIOType(tcp);
    }
    
    1. 接口 IDataIO 定义了所有 IO 类型应实现的基础。

    2. 从 IDataIO 派生的 SerialIO、TcpIO、UdpIO 类实现方法 SetStrategy() 以满足各自的需求。

    3. IO类拥有一个字段(命名端口),引用一个IDataIO类型,该字段可以在运行时通过调用IO中定义的SetIOType()方法设置为某个IO类型 班级。一旦调用此方法,我们就知道“端口”字段所指的类型,并且 然后调用 SetStrategy() 方法,它将在 IO 类之一中运行覆盖的方法。

    4. IOManager 类是客户端。当它需要某种IO类型时,比如SerialIO,只需要新建一个IO类,通过传递一个SerialIO类实例调用SetIOType()方法,所有与SerialIO类型相关的逻辑都会自动设置。

    希望我的描述可以帮助到你。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多