【问题标题】:Cast concrete type to generic type?将具体类型转换为泛型类型?
【发布时间】:2012-02-24 05:05:31
【问题描述】:

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

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 会容易得多,但它会将确定具体返回类型的负担交给调用者而不是请求处理程序(它当然知道所有关于类型的事情)。

【问题讨论】:

  • 首先,该方法不应该被称为 MakeResponse,因为它返回一个 TResponse?但更笼统地说:如果您必须检查事物的类型并且对某些特定类型采取一些特定操作,那么 您首先不是在编写通用代码,那么您为什么要使用 泛型?如果您有特殊的逻辑知道如何将 FooRequest 转换为 FooResponse 则创建一个方法,该方法接受 FooRequest 并返回 FooResponse;不需要泛型。
  • @Eric - 我正在研究一种责任链模式,其中路由节点只是向前传递消息,也许中间的某个节点会识别请求并返回正确的响应。不过,我应该在代码示例中说明清楚。
  • @forcey CoR 没有暗示任何关于泛型的内容,并且匹配实例类型不是泛型的目的。只需传递 BaseRequests 并使用 if (request is FooRequest) { ... }
  • @ChrisShain 使用泛型,调用站点的类型会看起来更强大:FooResponse response = chain.MakeRequest(new FooRequest)。换句话说,将BaseResponse->FooResponse 转换为消息处理程序而不是调用站点。

标签: c# generics


【解决方案1】:

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

也就是说,你告诉编译器“我知道的类型信息比你多”的方式是通过强制转换。

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

转换为对象然后转换为 TResponse 告诉编译器“我知道从响应到 TResponse 有一个标识、拆箱或引用转换”。如果你错了,你会在运行时得到一个异常。

【讨论】:

  • 谢谢 - 看起来我可以将 FooResponse 转换为 BaseResponse,然后转换为 TResponse。我仍然会争辩说我没有滥用它:)
  • @forcey,泛型应该是泛型的。如果您在处理泛型参数时进行强制转换,您很可能会滥用系统。
  • @RomanR。 - 很公平,我意识到我只是在使用泛型提供的类型推断系统,而不是按应有的方式使用泛型。
【解决方案2】:

在我看来,您应该从非通用版本BaseRequest 派生您的BaseRequest&lt;T&gt; 类,然后将您的函数编写为:

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);
}

但这看起来真的很令人费解。无论如何,我会让你反思一下

【讨论】:

  • 非常感谢,这确实很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多