Reputation: 29427
I have the following interfaces
public interface IMessageData {}
public interface IMessageSender<T> where T : class, IMessageData, new()
{
Task<T> BuildType();
Task SendMessage(T message);
}
and then a base implementation for the IMessageSender
public abstract class MessageSenderBase<T> : IMessageSender<T> where T : class, IMessageData, new()
{
public MessageSenderBase() {
}
public abstract Task<T> BuildType();
public async Task SendMessage(T message) {
Console.WriteLine($"Sending {JsonConvert.SerializeObject(message)}");
await Task.FromResult(0);
}
}
To use these interfaces I usually create a new instance of a MessageData and create a specific implementation of MessageSenderBase like
public class MessageDataA : IMessageData {
public MessageDataA() {
}
public string Description { get; set; }
}
and
public class ASender : MessageSenderBase<MessageDataA> {
public override async Task<MessageDataA> BuildType() {
MessageDataA result = new MessageDataA() { Description = "description" };
return await Task.FromResult<MessageDataA>(result);
}
}
At runtime I only have in configuration the names of the MessageSenderBase
implementations and I need to create dynamically instances of these classes and invoke both methods. This is correctly achieved with this code:
var className = "ASender";
var buildMethodName = "BuildType";
var sendMethodName = "SendMessage";
Assembly assembly = Assembly.GetExecutingAssembly();
var type = assembly.GetType(className);
var instance = Activator.CreateInstance(type);
//invoke build method
var buildMethodInfo = type.GetMethod(buildMethodName);
dynamic buildAwaitable = buildMethodInfo.Invoke(instance, null);
var returnedType = await buildAwaitable;
//invoke send method
var sendMethodInfo = type.GetMethod(sendMethodName);
dynamic sendAwaitable = sendMethodInfo.Invoke(instance, new[] { returnedType });
await sendAwaitable;
My questions:
var instance = Activator.CreateInstance(type);
to a stronger type instead of leaving it as var? This would avoid me to call methods by using MethodInfo and Invoke.Upvotes: 0
Views: 232
Reputation: 39027
You can bypass the issue entirely by providing a non-generic interface. Looking closely, you don't actually need to expose the BuildType
method.
You would end up with a IMessageSender interface that exposes a single method:
public interface IMessageSender
{
Task SendMessage();
}
Then the abstract class provides the BuildType
method and connects everything together:
public abstract class MessageSenderBase<T> : IMessageSender where T : class, IMessageData, new()
{
public MessageSenderBase() {
}
protected abstract Task<T> BuildType();
public async Task SendMessage() {
var message = await BuildType();
Console.WriteLine($"Sending {JsonConvert.SerializeObject(message)}");
await Task.FromResult(0);
}
}
Upvotes: 1
Reputation: 27974
can I cast the var instance = Activator.CreateInstance(type); to a stronger type instead of leaving it as var? This would avoid me to call methods by using MethodInfo and Invoke.
var
doesn't mean untyped. It's of the type of the right-hand side expression. In this case, it's an object
, but you can cast it to the actually expected type. However, in your dynamic scenario, there's more work needed (see below).
As a further suggestion is there any way to make this design more strong typed?
A common solution is to have a non-generic interface for situations like you describe. The advantage is you can call the methods directly, although you have to rely on object
instead of some specific type provided via the generic argument T
.
The base contract of your interfaces will then look like this:
public interface IMessageData {}
public interface IMessageSender
{
Task<object> BuildType();
Task SendMessage(object message);
}
public interface IMessageSender<T> : IMessageSender where T : class, IMessageData, new()
{
Task<T> BuildType();
Task SendMessage(T message);
}
In the base implementation, an explicit implementation of IMessageSender
will help you to hide the weakly-typed interface from its generic counterpart. The implementation will just pass the calls to the generic methods:
public abstract class MessageSenderBase<T> : IMessageSender<T> where T : class, IMessageData, new()
{
public MessageSenderBase() {
}
Task<object> IMessageSender.BuildType() {
T result = await BuildType();
return result;
}
Task IMessageSender.SendMessage(object message) {
return SendMessage((T)message);
}
public abstract Task<T> BuildType();
public async Task SendMessage(T message) {
…
}
}
The usage is then far simpler than the original code:
var className = "ASender";
var buildMethodName = "BuildType";
var sendMethodName = "SendMessage";
Assembly assembly = Assembly.GetExecutingAssembly();
var type = assembly.GetType(className);
IMessageSender instance = (IMessageSender)Activator.CreateInstance(type);
//invoke build method
var result = await instance.BuildType();
//invoke send method
await instance.SendMessage(result);
Upvotes: 1