繁体   English   中英

是否可以对泛型方法进行不安全的协变调用?

[英]Is it possible to do an unsafe covariant invocation of a generic method?

我有许多用于处理特定类型对象的类。

例如,

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#的方式解决...所以我也对其他方法感兴趣。

谢谢!

我怀疑如果你更进一步,动态类型将会起作用:

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

请注意我是如何使message动态化的,以便使用对象的实际类型而不是object来应用重载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);
}

有个更好的方法。 只需将演员嵌入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());
        }
    }
}

不涉及任何字符串...当然,如果您想通过命名来建立约定,则可以使扫描更容易,只需在注释中从消息类型的名称中查找处理程序类型。 您还可以使用IOC容器替换Activator.CreateInstance。

你有使用反射的解决方案:)

这是我所知道的唯一方法。 我在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());

    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM