I've got a few questions about how to provide both synchronous and asynchronous implementation of the same functionality in a library. I am gonna ask them first and then provide the example code below (which is actually quite a bit, but in fact it's quite simple).
Is there an approach to avoid violating the DRY principle? Consider the implementations of
JsonStreamReader.Read
,JsonStreamWriter.Write
,JsonStreamWriter.Flush
,ProtocolMessenger.Send
,ProtocolMessenger.Receive
and their asynchronous versions.Is there an approach to avoid violating the DRY principle when unit testing both synchronous and asynchronous versions of the same method? I am using NUnit, although I guess all frameworks should be the same in this regard.
How should be implemented a method returning
Task
orTask<Something>
considering theTake 1
andTake 2
variants ofComplexClass.Send
andComplexClass.Receive
? Which one is correct and why?Is it correct to always include
.ConfigureAwait(false)
afterawait
in a library considering it is not known where the library will be used (Console application, Windows Forms, WPF, ASP.NET)?
And here follows the code I am referring to in the first questions.
IWriter
and JsonStreamWriter
:
public interface IWriter
{
void Write(object obj);
Task WriteAsync(object obj);
void Flush();
Task FlushAsync();
}
public class JsonStreamWriter : IWriter
{
private readonly Stream _stream;
public JsonStreamWriter(Stream stream)
{
_stream = stream;
}
public void Write(object obj)
{
string json = JsonConvert.SerializeObject(obj);
byte[] bytes = Encoding.UTF8.GetBytes(json);
_stream.Write(bytes, 0, bytes.Length);
}
public async Task WriteAsync(object obj)
{
string json = JsonConvert.SerializeObject(obj);
byte[] bytes = Encoding.UTF8.GetBytes(json);
await _stream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
}
public void Flush()
{
_stream.Flush();
}
public async Task FlushAsync()
{
await _stream.FlushAsync().ConfigureAwait(false);
}
}
IReader
and JsonStreamReader
:
public interface IReader
{
object Read(Type objectType);
Task<object> ReadAsync(Type objectType);
}
public class JsonStreamReader : IReader
{
private readonly Stream _stream;
public JsonStreamReader(Stream stream)
{
_stream = stream;
}
public object Read(Type objectType)
{
byte[] bytes = new byte[1024];
int bytesRead = _stream.Read(bytes, 0, bytes.Length);
string json = Encoding.UTF8.GetString(bytes, 0, bytesRead);
object obj = JsonConvert.DeserializeObject(json, objectType);
return obj;
}
public async Task<object> ReadAsync(Type objectType)
{
byte[] bytes = new byte[1024];
int bytesRead = await _stream.ReadAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
string json = Encoding.UTF8.GetString(bytes, 0, bytesRead);
object obj = JsonConvert.DeserializeObject(json, objectType);
return obj;
}
}
IMessenger
and ProtocolMessenger
:
public interface IMessenger
{
void Send(object message);
Task SendAsync(object message);
object Receive();
Task<object> ReceiveAsync();
}
public interface IMessageDescriptor
{
string GetMessageName(Type messageType);
Type GetMessageType(string messageName);
}
public class Header
{
public string MessageName { get; set; }
}
public class ProtocolMessenger : IMessenger
{
private readonly IMessageDescriptor _messageDescriptor;
private readonly IWriter _writer;
private readonly IReader _reader;
public ProtocolMessenger(IMessageDescriptor messageDescriptor, IWriter writer, IReader reader)
{
_messageDescriptor = messageDescriptor;
_writer = writer;
_reader = reader;
}
public void Send(object message)
{
Header header = new Header();
header.MessageName = _messageDescriptor.GetMessageName(message.GetType());
_writer.Write(header);
_writer.Write(message);
_writer.Flush();
}
public async Task SendAsync(object message)
{
Header header = new Header();
header.MessageName = _messageDescriptor.GetMessageName(message.GetType());
await _writer.WriteAsync(header).ConfigureAwait(false);
await _writer.WriteAsync(message).ConfigureAwait(false);
await _writer.FlushAsync().ConfigureAwait(false);
}
public object Receive()
{
Header header = (Header)_reader.Read(typeof(Header));
Type messageType = _messageDescriptor.GetMessageType(header.MessageName);
object message = _reader.Read(messageType);
return message;
}
public async Task<object> ReceiveAsync()
{
Header header = (Header)await _reader.ReadAsync(typeof(Header)).ConfigureAwait(false);
Type messageType = _messageDescriptor.GetMessageType(header.MessageName);
object message = await _reader.ReadAsync(messageType).ConfigureAwait(false);
return message;
}
}
ComplexClass
:
public interface ISomeOtherInterface
{
void DoSomething();
}
public class ComplexClass : IMessenger, ISomeOtherInterface
{
private readonly IMessenger _messenger;
private readonly ISomeOtherInterface _someOtherInterface;
public ComplexClass(IMessenger messenger, ISomeOtherInterface someOtherInterface)
{
_messenger = messenger;
_someOtherInterface = someOtherInterface;
}
public void DoSomething()
{
_someOtherInterface.DoSomething();
}
public void Send(object message)
{
_messenger.Send(message);
}
// Take 1
public Task SendAsync(object message)
{
return _messenger.SendAsync(message);
}
// Take 2
public async Task SendAsync(object message)
{
await _messenger.SendAsync(message).ConfigureAwait(false);
}
public object Receive()
{
return _messenger.Receive();
}
// Take 1
public Task<object> ReceiveAsync()
{
return _messenger.ReceiveAsync();
}
// Take 2
public async Task<object> ReceiveAsync()
{
return await _messenger.ReceiveAsync().ConfigureAwait(false);
}
}