Is it possible to make an unsafe covariant call to a common method?

I have several classes designed to handle objects of certain types.

eg.

class FooHandler : Handler<Foo> { void ProcessMessage(Foo foo); } 

where the handler interface can be defined something like this:

 interface Handler<T> { void ProcessMessage(T obj); } 

Now I would like to use the dictionary of these handlers:

 Dictionary<Type, Handler> handlers; void ProcessMessage(object message) { var handler = handlers[message.GetType()]; handler.ProcessMessage(handler); } 

However, C # prevents me from using the Handler interface without specifying a type. C # also does not allow declaring an interface Handler<out T> , so I cannot use Handler<object> in a handler declaration.

Even this does not work:

 Dictionary<Type, object> handlers; void ProcessMessage(object message) { dynamic handler = handlers[message.GetType()]; handler.ProcessMessage(message); } 

This seems solvable using reflection:

 handler.GetType().GetMethod("ProcessMessage").Invoke(handler, new object[] { message }); 

And of course, I could remove the generics from the Handler interface. However, the reason I went this route is because I wanted to make the API for handlers as simple as possible. I wanted the classes to define the messages they receive, and that they could process these messages without having to enter parameters in each method.

I would rather avoid reflection, if possible, and generally avoid generics, not entirely satisfactory.

Am I missing something obvious or am I pushing the limits of C # generics?

I understand that C # is not Java (with Java type erasure, that would be easy), and maybe it could be better resolved in a more similar way to C # ... so I'm also interested in other approaches.

Thanks!

+6
source share
4 answers

There is a better way. Just insert the cast into lambda and save the action, not the handler:

  Dictionary<Type, Action<object>> handlers; void AddHandler<T>( Handler<T> handler ) { handlers.Add(typeof(T), m => handler.ProcessMessage((T)m)); } void ProcessMessage(object message) { var handler = handlers[message.GetType()]; handler(message); } 

OK, this is a bit beyond the scope of the question, but the discussion in the comments led us here:

 interface IMessage {} class Foo : IMessage {} interface Handler<T> where T : IMessage { void ProcessMessage(T obj); } class FooHandler : Handler<Foo> { public void ProcessMessage(Foo foo) {} } class Program { static readonly Dictionary<Type, Action<object>> handlers = new Dictionary<Type, Action<object>>(); static void AddHandler<T>(Handler<T> handler) where T : IMessage { handlers.Add(typeof(T), m => handler.ProcessMessage((T)m)); } static void ProcessMessage(object message) { var handler = handlers[message.GetType()]; handler(message); } public static IEnumerable<Type> GetAllTypes() { return AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()); } public static IEnumerable<Type> GetDerivedFrom<T>() { return GetAllTypes().Where(t => IsDerivedFrom(t, typeof(T))); } static bool IsDerivedFrom(Type t, Type parent) { return parent.IsAssignableFrom(t) && t!=parent; } static void Main() { var handlerTypes = from handlerBaseType in GetDerivedFrom<IMessage>().Select(t => typeof(Handler<>).MakeGenericType(t)) select GetAllTypes().FirstOrDefault(t => IsDerivedFrom(t, handlerBaseType)) into handlerType where handlerType!=null select Activator.CreateInstance(handlerType); foreach (object handler in handlerTypes) { AddHandler((dynamic)handler); Console.WriteLine("Registered {0}.", handler.GetType()); } } } 

No lines are required ... Of course, if you want to establish a naming convention, you can simplify the scan and just look at the type of the handler with the name of the message type, as it was done in your comment. You can also replace Activator.CreateInstance with an IOC container.

+5
source

I suspect dynamic printing will work if you take one more step:

 void ProcessMessage(dynamic message) { dynamic handler = handlers[message.GetType()]; handler.ProcessMessage(message); } 

Notice how I made a dynamic message , so that overload resolution will be applied using the actual type of the object, not using object .

Basically, what you're trying to do is not statically type safe, so the C # compiler doesn't let you handle it. This is pretty typical in situations where you only have a Type value at runtime.

Another option is to have a common method on your own and execute it either with reflection or with dynamic:

 void ProcessMessage(dynamic message) { // This will call the generic method with the right // type for T inferred from the actual type of the object // message refers to. ProcessMessageImpl(message); } void ProcessMessageImpl<T>(T message) { Handler<T> handler = (Handler<T>) handlers[typeof(T)]; handler.ProcessMessage(message); } 
+5
source

You have a solution in using reflection :)

This is the only way I know this. I use this exact approach in my ESB.


EDIT

There is a caveat to using other approaches (maybe I'm wrong, so please comment if I am disconnected). This is normal when you register a "singleton" handler with a known message type up. Using a common <T> means you know the type. However, in my scenario, I use transient handlers depending on the type of message that arrives. Therefore, I create a new handler.

This means that everything should be done on the fly, which requires reflection (with the possible exception of using dynamic in .net 4 --- I included .net 3.5).

Ideas?

+1
source

I ended up working with John's idea, which is presented here.

 public interface IHandler { void ProcessMessage(dynamic message); } public class Handler<T> : IHandler { public void ProcessMessage(dynamic message) { } } public class A{} public class B{} public class C{} public class D{} public class HandlerA : IHandler{ public void ProcessMessage(dynamic message){} } public class HandlerB : IHandler{ public void ProcessMessage(dynamic message) {} } public class HandlerC : IHandler{ public void ProcessMessage(dynamic message) {} } public class MsgProcessor { public Dictionary<Type, IHandler> handlers = new Dictionary<Type, IHandler>(); public void AddHandler<T>(T o) where T: IHandler { handlers.Add(typeof(T),o); } public void ProcessMessage(dynamic message) { ProcessMessage(message); } public void ProcessMessage<T>(T message) { Handler<T> handler = (Handler<T>) handlers[typeof (T)]; handler.ProcessMessage(message); } } public class Test { public void test() { var mp = new MsgProcessor(); mp.AddHandler<HandlerA>(new HandlerA()); mp.AddHandler<HandlerB>(new HandlerB()); mp.AddHandler<HandlerC>(new HandlerC()); mp.ProcessMessage(new A()); mp.ProcessMessage(new B()); mp.ProcessMessage(new C()); } } 
0
source

Source: https://habr.com/ru/post/893659/


All Articles