C#泛型处理程序,我有什么误解?

时间:2013-03-30 20:47:09

标签: c# .net generics event-handling

我不确定为什么这不起作用。即使TResponse是IResponse,它也不喜欢out和handlerMap添加的TResponse?我想我必须误解一些关于泛型,或者更可能是关于C#的东西。为什么这不起作用,是否有更好的方法来完成我在这里尝试做的事情?

private static Dictionary<Type, List<IResponseHandler<IResponse>>> _handlerMap;

public static void AddResponseHandler<TResponse>(IResponseHandler<TResponse> handler) where TResponse : IResponse
{
    List<IResponseHandler<TResponse>> handlers;
    _handlerMap.TryGetValue(typeof (TResponse), out handlers);

    if (handlers == null)
    {
        handlers = new List<IResponseHandler<TResponse>>();
        _handlerMap.Add(typeof (TResponse), handlers);
    }

    handlers.Add(handler);
}

public interface IResponseHandler<TResponse> where TResponse : IResponse
{
    void Handle(TResponse response);
}

我在编译期间遇到这些错误:

  

错误1'System.Collections.Generic.Dictionary&gt;&gt; .TryGe tValue(System.Type,out System.Collections.Generic.List&gt;)'的最佳重载方法匹配有一些无效的参数C:.. 。\ NetworkManager.cs 39 13汇编-CSharp-vs

     

错误2参数2:无法从'out System.Collections.Generic.List&gt;'转换'out System.Collections.Generic.List&gt;' C:... \ NetworkManager.cs 39 61 Assembly-CSharp-vs

If I change TResponse to IResponse within the method, everything above
handlers.Add(handler) compiles fine. I don't understand why I can't add a handler of 
<TResponse : IResponse> to a List<IResponseHandler<IReponse>>?

3 个答案:

答案 0 :(得分:1)

即使IResponseHandler<IResponse>上有where子句,C#中的差异也不允许您将IResponseHandler<T>分配给T

我不知道你要做什么,因为你没有提供所有在这里使用的代码;但是,这将编译:

public class SomeClass<TResponse> where TResponse : IResponse
{
    private static Dictionary<Type, List<IResponseHandler<TResponse>>> _handlerMap;

    public static void AddResponseHandler(IResponseHandler<TResponse> handler) 
    {
        List<IResponseHandler<TResponse>> handlers;
        _handlerMap.TryGetValue(typeof(TResponse), out handlers);

        if (handlers == null)
        {
            handlers = new List<IResponseHandler<TResponse>>();
            _handlerMap.Add(typeof(TResponse), handlers);
        }

        handlers.Add(handler);
    }       
}

这会将泛型从方法移动到类,因此您可以定义兼容的_handlerMap

答案 1 :(得分:1)

正如其他人所说的那样 - 没有办法做到这一点`就像你做的那样'......

a)您需要contravariance - Add才能工作

b)您需要covariance才能upcastIResponseHandler<TResponse>IResponseHandler<IResponse>

(还有另一个编译问题,将out返回到不同类型的List中,这两种方法无法正常工作)...

对于解决方案 - 如果trick满足您的需求,您可以contract使其成为工作类型。这更像是一个“实践范例”,因为你失去了一些支持 - 但取决于你需要什么......

interface IResponse { }
interface IResponseHandler<out TResponse>
    where TResponse : class, IResponse
{
    // add 'read-only' (simplified) properties only - that support 'covariance' - meaning no 'input parameters' of T etc.
    // void Handle(TResponse response);
}
abstract class ResponseHandler<TResponse> : IResponseHandler<TResponse> 
    where TResponse : class, IResponse
{
    public abstract void Handle(TResponse response);
}
class TestHandler
{
    private static Dictionary<Type, List<IResponseHandler<IResponse>>> _handlerMap = new Dictionary<Type,List<IResponseHandler<IResponse>>>();
    public static void AddResponseHandler<TResponse>(IResponseHandler<TResponse> handler) where TResponse : class, IResponse
    {
        List<IResponseHandler<IResponse>> handlers;
        _handlerMap.TryGetValue(typeof(TResponse), out handlers);
        if (handlers == null)
        {
            handlers = new List<IResponseHandler<IResponse>>();
            _handlerMap.Add(typeof(TResponse), handlers);
        }
        IResponseHandler<IResponse> myhandler = handler;
        handlers.Add(myhandler);
    }
    public static void Handle<TResponse>(TResponse response) where TResponse : class, IResponse
    {
        List<IResponseHandler<IResponse>> handlers;
        _handlerMap.TryGetValue(typeof(TResponse), out handlers);
        if (handlers == null) return;
        foreach (var handler in handlers)
        {
            (handler as ResponseHandler<TResponse>).Handle(response);
        }
    }
}
// and implementation...
class FirstResponse : IResponse { }
class AutomatedResponse : IResponse { }
class FirstHandler : ResponseHandler<FirstResponse>
{
    public override void Handle(FirstResponse response) { }
}
class AutomatedHandler : ResponseHandler<AutomatedResponse>
{
    public override void Handle(AutomatedResponse response) { }
}
// ...and a test...
var firsthandler = new FirstHandler();
var secondhandler = new AutomatedHandler();
TestHandler.AddResponseHandler(firsthandler);
TestHandler.AddResponseHandler(secondhandler);

var first = new FirstResponse();
var second = new AutomatedResponse();
TestHandler.Handle(first);
TestHandler.Handle(second);

有几件有趣的事情,很快......

1)out上需要base interface - 才能covariant

2)您需要keep it协变 - 不要在Add中添加任何内容(请参阅注释)。基本上(并且过度简化)你需要维护它read only(标记这不是真的 - 只是更容易这样思考)。这也适用于参与其中的所有类型/其他参数等。编译器将指导您错误

3)将所有功能从IResponseHandler拉出到ResponseHandler类 - 该服务器全部 - 您可以添加Add等等 - 并覆盖特定情况

4)你需要cast才能进入实际上可以“处理”的“处理程序” - (handler as ResponseHandler<TResponse>).Handle(response);

注意

...如果你的'处理程序'只是'处理'(futile是你真正需要的唯一方法),这完全是Add - 这完全取决于你的代码和结构和事物的实施。如果您的基本界面“用于其他目的” - 那么它可能是值得的。否则 - 您可以使用object完成所有这些操作 - 并从object投射,您将不会更喜欢它。

答案 2 :(得分:0)

进一步扩展我的评论,您的IResponseHandler<T>界面在<{1}}上是逆变T出现在“输入”位置)。没有办法做你想要的,因为它不是类型安全的。

要窃取Eric Lippert喜欢使用的类比,如果香蕉是水果,那么认为一碗香蕉是一碗水果似乎是合理的。但是,如果你问这个碗里有什么东西,这只是类型安全的吗?如果你尝试添加到碗里,那就错了。一碗水果应该能够接受任何水果。但是,如果我们可以将你的一碗香蕉视为一碗水果,那么你可以在一碗香蕉中加入橙子并弄得一团糟。

编译器阻止您出现这种不一致。您的T个对象无法接受任何IResponseHandler<T>,只能接受特定的IResponse类型。