2

我不确定为什么这不起作用。它不喜欢 TResponse 用于 out 和 handlerMap 添加,即使 TResponse 是 IResponse?我想我一定是对泛型有误解,或者更可能是对 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>>.TryGe‌​tValue(System.Type, out System.Collections.Generic.List>)' 的最佳重载方法匹配有一些无效参数 C:...\NetworkManager。 cs 39 13 组装‌​-CSharp-vs

错误 2 参数 2:无法从 'out System.Collections.Generic.List>' 转换为 'out System.Collections.Generic.List>' 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>>?
4

3 回答 3

1

C# 中的差异不允许您将 an 分配给 an IResponseHandler<IResponse>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.

于 2013-03-30T21:10:29.433 回答
1

正如其他人所提到的 - 没有办法做到“你正在做的方式”......

a) 你需要contravariance- 为了Add工作

b) 你需要covariance能够upcastIResponseHandler<TResponse>IResponseHandler<IResponse>

(你也有另一个编译问题,返回out到不同类型的列表,这两种方式都不能工作)......

对于一个解决方案 -如果这满足您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下来,你不会对此感到更快乐或更快乐。

于 2013-03-30T21:43:14.650 回答
0

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

借用 Eric Lippert 喜欢使用的一个类比,如果香蕉是一种水果,那么认为一碗香蕉就是一碗水果似乎是合理的。然而,如果你问这个碗里有什么,这只是类型安全的?如果你试着往碗里加东西,那就错了。一碗水果应该可以接受任何水果。但是,如果我们可以将您的一碗香蕉视为一碗水果,那么您可以在一碗香蕉中添加一个橙子,然后一团糟。

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

于 2013-03-30T21:20:24.880 回答