将具体的混凝土类型转换为通用类型?

7

我正在尝试编译以下代码:

public class BaseRequest<TResponse> where TResponse : BaseResponse {}
public class BaseResponse {}

public class FooRequest : BaseRequest<FooResponse> {}
public class FooResponse : BaseResponse {}

...

public TResponse MakeRequest<TResponse>(BaseRequest<TResponse> request)
     where TResponse : BaseResponse
{
}

我希望能够调用MakeRequest(new FooRequest())方法并将返回值作为FooResponse。被调用者不需要知道FooRequest,可以将其传递给另一个处理程序。尽管签名是正确的,但我无法实现MakeRequest方法。如果我这样实现:

public TResponse MakeRequest<TResponse>(BaseRequest<TResponse> request)
     where TResponse : BaseResponse
{
    FooRequest fooRequest = request as FooRequest;
    if (fooRequest != null)   // if I can handle the request, handle it
    {
        return new FooResponse(...);   // ***
    }

    BarRequest barRequest = request as BarRequest;
    if (barRequest != null) 
    {
        return new BarResponse(...);  
    }

    else                      // otherwise, pass it on to the next node
    {
        // maybe it will handle a BazRequest, who knows
        return nextNode.MakeRequest(request);
    }
}

但是,***行无法编译,因为编译器不知道FooResponseTResponse。我知道这是因为它在FooRequest中指定了。有没有任何方法可以避免涉及令人讨厌的反射而解决这个问题(如果要使用反射的话,我更愿意返回BaseResponse)?
谢谢。
更新:我使用泛型来强制返回类型,因此调用者知道应该期望什么。在这里只返回BaseResponse会容易得多,但这将把找出具体返回类型的负担转移到调用者身上,而不是请求处理程序(当然,请求处理程序对类型信息非常了解)。

7
首先,这个方法应该叫做MakeResponse,因为它返回一个TResponse。但更普遍地说:如果你必须检查某个东西的类型,并对某些特定类型采取一些特定行动,那么你首先就没有编写通用代码,所以为什么要使用泛型呢?如果你有特殊逻辑,知道如何将FooRequest转换为FooResponse,那么创建一个方法,接收FooRequest并返回FooResponse即可,不需要使用泛型。 - Eric Lippert
@Eric - 我正在使用责任链模式进行编程,其中路由节点只是将消息传递下去,也许中间的某个节点会识别请求并返回正确的响应。不过我应该在代码示例中明确说明这一点。 - Todd Li
@forcey CoR并不涉及泛型,而且匹配实例类型也不是泛型的目的。只需传递BaseRequests并使用if (request is FooRequest) { ... } 即可。 - Chris Shain
使用泛型后,调用方看起来更加强类型化:FooResponse response = chain.MakeRequest(new FooRequest)。换句话说,在消息处理程序中将 BaseResponse 转换为 FooResponse,而不是在调用方处进行转换。 - Todd Li
2个回答

14

正如我在评论中所说的那样,我怀疑你这样做是错误的。这看起来像是泛型的滥用。

话虽如此,告诉编译器“我知道更多的类型信息比你知道的多”方法是通过强制转换。

var response = new FooResponse(...);   
return (TResponse)(object)response;

将response转换为object,然后再转换为TResponse可以告诉编译器:“我知道从response到TResponse有一个身份、拆箱或引用转换”。如果你错了,在运行时会抛出异常。


谢谢 - 看起来我可以将FooResponse转换为BaseResponse,然后再转换为TResponse。虽然我仍然会认为我没有滥用它 :) - Todd Li
1
@forcey,泛型应该是通用的。如果在处理泛型参数时进行强制转换,则很可能滥用了系统。 - Roman Royter
@RomanR. - 好吧,我意识到我只是使用泛型提供的类型推断系统,而没有按照它应该使用泛型的方式。 - Todd Li

2
在我看来,你应该从一个非泛型版本的BaseRequest派生出你的BaseRequest<T>类,然后编写以下函数:
public BaseResponse MakeRequest(BaseRequest request)

这对我来说似乎是正确的方式,因为你甚至没有在函数内引用类型。
对我来说,泛型只是语法糖。通过按照您所做的方式编写函数,您获得的好处是能够编写:
FooResponse r = MakeRequest(new FooRequest(...))

改为:

FooResponse r = (FooResponse)MakeRequest(new FooRequest(...))

所以好处并不是很大。而且你自己的代码让你混淆到无法看出缺失的部分是强制转换,这意味着这种泛型方式的代码可能比非泛型方式更不清晰。
哦,你的方法的另一个缺点是你将失去以下能力:
var requests = new List<BaseRequest> { new FooRequest(), new BarRequest() };
var responses = new List<BaseResponse> ();
foreach(var request in requests)
{
    responses.Add(MakeRequest(request));
}

或者你可以这样做:拥有以下内容:
public BaseResponse MakeRequest(BaseRequest request) { /* thing that does the work */ }
public TResponse MakeRequest<TResponse>(BaseRequest<TResponse> request)
{
    // Just for the nice syntax
    return (TResponse)MakeRequest(request);
}

但那看起来非常复杂。无论如何,我会让你反思这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接