正在尝试使用泛型合并类,无法工作
本文关键字:工作 合并 泛型 | 更新日期: 2023-09-27 18:30:04
我有三个类,我想讨论合并为一个类的问题。它们完全相同,除了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();
}
}
大多数情况下"否",这不是使用泛型的有效候选者,但由于存在由所有三个类实现的泛型接口IObservable<T>
,因此可以"在某种程度上"使用。
C#泛型导论说:
泛型允许您定义类型安全的数据结构,而不需要致力于实际的数据类型。这导致性能提升和更高质量的代码,因为您可以重用数据处理算法,而不复制特定类型的代码。
在这里,不可避免地被类型化为(三个特定的)数据类型,并且每个类型的代码都不相同,因此没有重用的潜力。
然而,有一个接口实现是通用的。
大多数情况下,我相信你真正想要的是好的老式继承,因为有很多常见的"样板"代码,这些代码不是通用的(特定于类型),但对所有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>
,我们可以逃脱惩罚。
您可以这样做,但这意味着的部分实现将不是通用的。它必须由调用者提供,或者您必须有一些已知类型的"库存"实现。
这里有一个简单的例子:
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))
现在,所有这些都表明:在你做任何类似上面的事情之前,你应该非常确定你真的需要它是通用的。
这样做的一个原因是,例如,如果基类实际上需要操作泛型类型参数的类型的项,将它们存储在成员数组中,等等。但即使这样,组合也可能是更好的策略。也就是说,有一个特定于所使用的每个类型的非泛型类,包含该泛型类型的实例,它将该泛型类型支持的某些操作委托给该实例。
您的代码示例不够简单,无法很容易地看出(即,没有一些耗时的分析)这里的确切需求。但是,您真正需要的可能只是一个非泛型基类,它提供核心实现细节,并按特定类型的非泛型类进行细分,以处理操作中特定类型的部分。
不,我认为泛型在这里不合适——至少不是简化代码的一种方式。我想你需要整理一下。你的课做得太多了。如果我是你,我会介绍:
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
方法中。
我认为通过这种方式,您会得到更清晰、更可重用和可测试的代码。