是否可能对泛型方法进行不安全的协变调用
本文关键字:不安全 调用 泛型方法 是否 | 更新日期: 2023-09-27 18:03:53
我已经设计了一些类来处理特定类型的对象。
。
class FooHandler : Handler<Foo> {
void ProcessMessage(Foo foo);
}
处理程序接口可以这样定义:
interface Handler<T> {
void ProcessMessage(T obj);
}
现在,我希望能够使用这些处理程序的字典:
Dictionary<Type, Handler> handlers;
void ProcessMessage(object message) {
var handler = handlers[message.GetType()];
handler.ProcessMessage(handler);
}
然而,c#似乎不允许我在不指定类型的情况下使用Handler接口。c#也不允许我声明interface Handler<out T>
,所以我不能在处理程序声明中使用Handler<object>
。
这个也不行:
Dictionary<Type, object> handlers;
void ProcessMessage(object message) {
dynamic handler = handlers[message.GetType()];
handler.ProcessMessage(message);
}
这似乎可以通过反射来解决:
handler.GetType().GetMethod("ProcessMessage").Invoke(handler, new object[] { message });
当然,我还可以从Handler接口中删除泛型。然而,我选择这条路的原因是我希望使处理程序的API尽可能简单。我希望类能够指定它们接收到的消息,并且能够处理这些消息,而不必在每个方法中强制转换参数。
如果可能的话,我宁愿避免反射,完全避免泛型似乎不太令人满意。
我是否错过了一些明显的东西,或者我是否违反了c#泛型的限制?
我意识到c#不是Java (Java的类型擦除,这将是容易的),也许这可能已经更好地解决在一个更像c#的方式…所以我对其他方法也很感兴趣。
谢谢!
有更好的办法。只需在lambda中嵌入强制转换并存储动作而不是处理程序:
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);
}
好吧,这有点超出了问题的范围,但是评论中的讨论把我们带到了这里:
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());
}
}
}
不涉及字符串…当然,如果您希望通过命名来建立约定,您可以简化扫描,只需从消息类型的名称查找处理程序类型,就像您在注释中所做的那样。您还可以替换Activator。使用IOC容器创建实例
我怀疑如果你更进一步,动态类型将会工作:
void ProcessMessage(dynamic message) {
dynamic handler = handlers[message.GetType()];
handler.ProcessMessage(message);
}
注意我是如何使message
动态的,以便重载解析将使用对象的实际类型来应用,而不是object
。
基本上你试图做的不是静态类型安全的,这就是为什么c#编译器不让你摆脱它。这在执行时只有Type
值的情况下是相当典型的。
另一个选择是自己有一个泛型方法,并执行
,可以使用反射或动态: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);
}
你有你的解决方案在使用反射:)
这是我所知道的唯一方法。我在我的ESB中使用的正是这种方法。
编辑
使用其他方法有一个警告(我可能是错的,所以如果我偏离轨道,请评论)。当您预先使用已知的消息类型注册一个"单例"处理程序时,这是很好的。使用通用的<T>
意味着您知道该类型。但是,在我的场景中,我根据到达的消息类型使用瞬态处理程序。所以我创建了一个新的处理程序。
这意味着一切都必须在飞行中完成,因此需要反射(除了可能在。net 4中使用dynamic
—我在。net 3.5上)。
想法?
我最终实现了Jon的想法,在这里分享。
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());
}
}