试图使用泛型合并类,而不是工作

时间:2014-11-11 16:10:44

标签: c# generics serial-port system.reactive

我有3个课程,我想谈谈合并到一个班级。 它们完全相同,但Subscribe方法除外,更具体地说是observer.OnNext(...)

我想最终:

public class ObservableSerialPort<T> : IObservable<T>, IDisposable

然后实例化可能是:

var port = new ObservableSerialPort<byte[]>("COM4");

这是否是使用Generics的有效候选人?

public class ObservableSerialPort_bytearray : IObservable<byte[]>, IDisposable
{
    private readonly SerialPort _serialPort;

    public ObservableSerialPort_bytearray(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
    {
        _serialPort = new SerialPort()
        {
            PortName = portName,
            BaudRate = baudRate,
            Parity = parity,
            DataBits = dataBits,
            StopBits = stopBits,
            DtrEnable = true,
            RtsEnable = true,
            Encoding = new ASCIIEncoding(),
            ReadBufferSize = 4096,
            ReadTimeout = 10000,
            WriteBufferSize = 2048,
            WriteTimeout = 800,
            Handshake = Handshake.None,
            ParityReplace = 63,
            NewLine = "\n",
        };

        _serialPort.Open();
    }

    public IDisposable Subscribe(IObserver<byte[]> observer)
    {
        if (observer == null)
            throw new ArgumentNullException("observer");

        // Processing when the incoming event has occurred
        var rcvEvent = Observable.FromEventPattern<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(
            h => h.Invoke, h => _serialPort.DataReceived += h, h => _serialPort.DataReceived -= h)
            .Subscribe(e =>
            {
                if (e.EventArgs.EventType == SerialData.Eof)
                {
                    observer.OnCompleted();
                }
                else
                {
                    var buf = new byte[_serialPort.BytesToRead];
                    var len = _serialPort.Read(buf, 0, buf.Length);

                    // To notify the Observer that it had received data (byte[])
                    Observable.Range(0, len).ForEach(i => observer.OnNext(buf));
                }
            });


        // Processing when an error event occurs
        var errEvent = Observable.FromEventPattern<SerialErrorReceivedEventHandler, SerialErrorReceivedEventArgs>
            (h => _serialPort.ErrorReceived += h, h => _serialPort.ErrorReceived -= h)
            .Subscribe(e => observer.OnError(new Exception(e.EventArgs.EventType.ToString())));

        // cancel the event registration When Dispose is called
        return Disposable.Create(() =>
        {
            rcvEvent.Dispose();
            errEvent.Dispose();
        });
    }

    public void Send(string text)
    {
        _serialPort.Write(text);
    }

    public void Send(byte[] text)
    {
        _serialPort.Write(text, 0, text.Length);
    }

    public void Dispose()
    {
        _serialPort.Close();
    }
}
public class ObservableSerialPort_byte : IObservable<byte>, IDisposable
{
    private readonly SerialPort _serialPort;

    public ObservableSerialPort_byte(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
    {
        _serialPort = new SerialPort()
        {
            PortName = portName,
            BaudRate = baudRate,
            Parity = parity,
            DataBits = dataBits,
            StopBits = stopBits,
            DtrEnable = true,
            RtsEnable = true,
            Encoding = new ASCIIEncoding(),
            ReadBufferSize = 4096,
            ReadTimeout = 10000,
            WriteBufferSize = 2048,
            WriteTimeout = 800,
            Handshake = Handshake.None,
            ParityReplace = 63,
            NewLine = "\n",
        };

        _serialPort.Open();
    }

    public IDisposable Subscribe(IObserver<byte> observer)
    {
        if (observer == null) throw new ArgumentNullException("observer");

        // Processing when the incoming event has occurred
        var rcvEvent = Observable.FromEventPattern<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(
            h => h.Invoke, h => _serialPort.DataReceived += h, h => _serialPort.DataReceived -= h)
            .Subscribe(e =>
            {
                if (e.EventArgs.EventType == SerialData.Eof)
                {
                    observer.OnCompleted();
                }
                else
                {
                    var buf = new byte[_serialPort.BytesToRead];
                    var len = _serialPort.Read(buf, 0, buf.Length);

                    // To notify the Observer that it had received data one byte at a time
                    Observable.Range(0, len).ForEach(i => observer.OnNext(buf[i]));
                }
            });


        // Processing when an error event occurs
        var errEvent = Observable.FromEventPattern<SerialErrorReceivedEventHandler, SerialErrorReceivedEventArgs>
            (h => _serialPort.ErrorReceived += h, h => _serialPort.ErrorReceived -= h)
            .Subscribe(e =>
                {
                    observer.OnError(new Exception(e.EventArgs.EventType.ToString()));
                });

        // cancel the event registration When Dispose is called
        return Disposable.Create(() =>
        {
            rcvEvent.Dispose();
            errEvent.Dispose();
        });
    }

    public void Send(string text)
    {
        _serialPort.Write(text);
    }
    public void Send(byte[] text)
    {
        _serialPort.Write(text, 0, text.Length);
    }
    public void Dispose()
    {
        _serialPort.Close();
    }
}
public class ObservableSerialPort_string : IObservable<string>, IDisposable
{
    internal readonly SerialPort _serialPort;

    public ObservableSerialPort_string(string portName, int baudRate = 19200, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
    {
        _serialPort = new SerialPort()
        {
            PortName = portName,
            BaudRate = baudRate,
            Parity = parity,
            DataBits = dataBits,
            StopBits = stopBits,
            DtrEnable = true,
            RtsEnable = true,
            Encoding = new ASCIIEncoding(),
            ReadBufferSize = 4096,
            ReadTimeout = 10000,
            WriteBufferSize = 2048,
            WriteTimeout = 800,
            Handshake = Handshake.None,
            ParityReplace = 63,
            NewLine = "\n",
        };

        _serialPort.Open();
    }

    public IDisposable Subscribe(IObserver<string> observer)
    {
        if (observer == null) throw new ArgumentNullException("observer");

        // Processing when the incoming event has occurred
        var rcvEvent = Observable.FromEventPattern<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(h => h.Invoke, h => _serialPort.DataReceived += h, h => _serialPort.DataReceived -= h)
            .Select(e =>
            {
                if (e.EventArgs.EventType == SerialData.Eof)
                {
                    observer.OnCompleted();
                    return string.Empty;
                }

                // And converting the received data to a string
                var buf = new byte[_serialPort.BytesToRead];
                _serialPort.Read(buf, 0, buf.Length);
                return Encoding.ASCII.GetString(buf);
            })
            .Scan(Tuple.Create(new List<string>(), ""),
                  (t, s) =>
                  {
                      // I linked this time of received data s and the last remaining t.Item2.
                      var source = String.Concat(t.Item2, s);

                      // Minute to put in Item1 a newline code is attached, to notify the Observer.
                      // Amount of line feed code is not attached was placed in Item2, and processed when receiving the next data.
                      var items = source.Split('\n');
                      return Tuple.Create(items.Take(items.Length - 1).ToList(), items.Last());
                  })
            .SelectMany(x => x.Item1) // The Item1 only I will notify the Observer.
            .Subscribe(observer);

        // Processing when an error event occurs
        var errEvent = Observable.FromEventPattern<SerialErrorReceivedEventHandler, SerialErrorReceivedEventArgs>(h => _serialPort.ErrorReceived += h, h => _serialPort.ErrorReceived -= h)
            .Subscribe(e => observer.OnError(new Exception(e.EventArgs.EventType.ToString())));

        // cancel the event registration When Dispose is called
        return Disposable.Create(() =>
        {
            rcvEvent.Dispose();
            errEvent.Dispose();
        });
    }

    public void Send(string text)
    {
        _serialPort.Write(text);
    }
    public void Send(byte[] text)
    {
        _serialPort.Write(text, 0, text.Length);
    }

    public void Dispose()
    {
        _serialPort.Close();
    }
}

3 个答案:

答案 0 :(得分:0)

不,我不认为泛型在这里是合适的 - 至少不是简化代码的手段。我想你需要有点整洁。你的课程做得太多了。如果我是你,我会介绍:

class ObservableSerialPortReader : IObservable<byte>

这将通过工厂方法返回,以便您可以为每个串行端口设置单例 - 您不需要串行端口的多个读取器。这个类应该封装基本的读取功能,并且应该发布以允许多个订阅者访问热字节流。

现在,您可以创建使用此字节流的Rx运算符,并将其解析为更高的抽象。例如你可能有类似的东西:

IObservable<string> GetSerialDeviceMessage(IObservable<byte> serialPortBytes);
IObservable<byte[]> GetSerialDeviceData(IObservable<byte> serialPortBytes,
                                        long numBytes);

等。这就是我如何处理从串行端口获取不同数据类型的方法 - 请注意,对这些数据类型的订阅可能是短暂的,而不会影响基础ObservableSerialPortReader。例如您可以订阅GetSerialDeviceMessage,它会OnNext一个字符串,然后OnComplete,而不会影响基础热流。

然后,完全单独创建class SerialPortWriter。您可以按照相同的模式处理发送字节,然后由管理更高抽象的类包装,例如发送文本命令。

现在,您可以将上述类组合成顶级抽象,协调它们以管理读写。例如,这将使用上述组件订阅适当的解析器,发送消息并等待响应。我可以看到这种交换包含在一个async方法中。

我认为你会以这种方式获得更清晰,更可重用和可测试的代码。

答案 1 :(得分:0)

您可以这样做,但这意味着部分实施赢得是通用的。它必须由来电者提供,否则您必须拥有一些&#34; stock&#34;已知类型的实现。

这是一个简单的例子:

abstract class Base<T>
{
    public abstract T Decode(byte[] input);
}

class StringDecoder : Base<string>
{
    public override string Decode(byte[] input)
    {
        return Encoding.UTF8.GetString(input);
    }
}

或者,您可以将实现作为委托传递:

class GenericDecoder<T>
{
    private Func<byte[], T> _decoder;

    public GenericDecoder(Func<byte[], T> decoder)
    {
        _decoder = decoder;
    }

    public T Decode(byte[] input)
    {
        return _decoder(input);
    }
}

初始化如下:

new GenericDecoder<string>(x => Encoding.UTF8.GetString(x))

现在,所有这些都说:在你做上述任何事情之前,你应该非常确定你真的需要这是通用的。

这样做的一个原因是,例如,如果基类实际上需要操作泛型类型参数的类型的项目,将它们存储在成员数组中,等等。但即便如此,组合可能是更好的策略。即有一个特定于所使用的每个类型的非泛型类,其中包含泛型类型的实例,它委托该泛型类型支持的某些操作。

您的代码示例不够简单,因为它很明显(即没有一些耗时的分析)这里的具体需求是什么。但是,您可能真正需要的是一个非通用基类,它提供了核心实现细节,由类型特定的非泛型类进行细分,以处理操作中特定于类型的部分。

答案 2 :(得分:0)

主要是&#34; no&#34;,这不是使用泛型的有效候选人,但是有资格&#34;有些&#34;因为所有三个类都实现了通用接口 - IObservable<T>

An Introduction to C# Generics说:

  

泛型允许您在没有的情况下定义类型安全的数据结构   提交实际数据类型。这导致了重大意义   性能提升和更高质量的代码,因为你可以重用   数据处理算法,不需要复制特定于类型的代码。

这里 不可避免地类型提交到(三种特定的)数据类型,并且每种类型的代码不一样因此没有重用潜力

然而,有一个通用的接口实现。

大多数情况下,我相信你真正想要的是良好的老式继承,因为 很多共同的&#34;样板&#34;代码,它不是通用的(类型特定的),但对所有ObservableSerialPort类都是通用的。让我们从那开始吧。

将公共代码移动到新的基类:

public abstract class ObservableSerialPort : IDisposable
{
    protected readonly SerialPort _serialPort;

    protected ObservableSerialPort(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
    {
        _serialPort = new SerialPort()
        {
            // common SerialPort construction code here. Removed to make answer more readable.
        };

        _serialPort.Open();
    }

    public void Send(string text)
    {
        _serialPort.Write(text);
    }

    public void Send(byte[] text)
    {
        _serialPort.Write(text, 0, text.Length);
    }

    public void Dispose()
    {
        _serialPort.Close();
    }
}

保留您的三个类,但作为基类的派生类,现在重构了公共代码:

public class ObservableSerialPort_byte : ObservableSerialPort, IObservable<byte>
{
    public ObservableSerialPort_byte(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
    : base(portName, baudRate, parity, dataBits, stopBits) { }

    public IDisposable Subscribe(IObserver<byte> observer)
    {

在第二次重构浪潮中,我们可以通过将基类定义更改为更接近您所要求的内容:

public abstract class ObservableSerialPort<T> : IDisposable, IObservable<T>

并添加到基类:

public abstract IDisposable Subscribe(IObserver<T> observer);

现在是第一个派生类:

public class ObservableSerialPort_bytearray : ObservableSerialPort<byte[]>
{
    public ObservableSerialPort_bytearray(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
    : base(portName, baudRate, parity, dataBits, stopBits) { }

    public override IDisposable Subscribe(IObserver<byte[]> observer)
    {

第二个是:

public class ObservableSerialPort_byte : ObservableSerialPort<byte>
{
    public ObservableSerialPort_byte(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
    : base(portName, baudRate, parity, dataBits, stopBits) { }

    public override IDisposable Subscribe(IObserver<byte> observer)
    {

最重要的是,Subscribe()中的一些代码是常见的(非通用的),因此您可以将其重构为基类中的受保护函数。

注意,从技术上讲,你可以创建一个通用类来检查三种具体类型T中的哪一种,但是a)没有合适的where约束,所以你可以事实上用其他无效的类型实例化,例如,double和b)然后你必须进行类型比较,这种类型比较根本不是通用的,而且完全没有意义。回到最初的引用,这样做不会让你无法承诺实际的数据类型,不会给你更高质量的代码,并且不会重复使用类型特定的代码而不会重复使用数据处理算法解决方案如上我的第二个解决方案可能会受到同样的批评,但我认为如果基类需要实现IObservable<T>,我们就可以侥幸成功。