我可以拥有/使用带有List <class <t>&gt;的字段/属性吗?哪个T可以是什么?</class <t>

时间:2014-05-23 10:32:00

标签: c# generics types .net-4.5 covariance

原始问题

我试图为我的网络请求制作一个通用类

internal class Request<TRequest, TResponse>
    where TRequest : class
    where TResponse : class
{
    public Uri RequestUri;
    public TRequest RequestItem;
    public TResponse ResponseItem;
    ...
}

类型TRequestTResponse用于使用XML,JSON等序列化和反序列化。

现在我想将这些Request<>存储在列表中,以便进行排队,缓存和错误处理。所以我试着宣布:

private List<Request<object, object>> requests;

但是,我无法在此请求列表中添加任何内容,因为当我尝试时:

var a = new Request<FooRequest, BarResponse>();
requests.Add(a);

我明白了:

  

无法从My.Services.Request<FooRequest,BarResponse>转换为My.Services.Request<object,object>

是否有办法获得混合类型请求的类型列表(Request<a,b>Request<c,d>)?或者更好的方法来解决这个问题?

我正在编写.NET 4.5(Windows Phone,Windows Store)。


首先编辑

经过多次评论,一些不满意的答案以及重复的投票后,我将在下面回顾一下基于这一切的尝试:

  • 使用interface IRequest<out T, out T1>

    嗯,界面不能有一个字段。所以我必须将列表的项目强制转换为Request<>的实例才能访问我想要的内容。我不确定如何编写此演员来检索两个变体(TRequestTResponse),我们将不胜感激。但是通过系统地投射,它与最终使用List<object>是否相同?

    或者我可以在界面中设置属性。但它告诉我,我无法使用关键字out

      

    无效方差:类型参数&#39; TRequest&#39;必须在My.Services.IRequest<TRequest,TResponse>.RequestItem上无条件有效。 &#39; TRequest&#39;是协变的。

    所以我可以删除关键字out,但在尝试将项目添加到列表时,它再次告诉我:

      

    无法从My.Services.Request<FooRequest,BarResponse>转换为My.Services.IRequest<object,object>

  • 使用abstract class BaseRequest

    几乎相同的问题,我的基类不能有TRequestTResponse类型的字段;如何检索这两个变种?我需要投射列表中的项目。所以List<object>也会一样好。

  • 使用泛型通配符

    不存在,可能永远不会存在。

  • 检查C#协方差

    提供的答案太模糊,没有什么可以实现的。甚至不是link to MSDN。我明确地谈到了List<>这是一种协变类型。解决这个问题并不容易,因为 man google

  • 检查主题:匿名类的通用列表

    绝对不重复,因为问题/答案不适用于存储到打字字段/属性:

    var list = new[] { a }.ToList();
    

    这只是一个List<Request<TRequest, TResponse>>,遗憾的是,我无法在方法之外的类中声明类型为var的任何内容。

  • List<object>List<dynamic>

    同样,不是List<Class<T>>的示例。但是,让我们试试List<Class<dynamic,dynamic>> ......失败:

      

    无法从My.Services.Request<TRequest,TResponse>转换为My.Services.Request<dynamic,dynamic>

  • 检查主题: C# - Multiple generic types in one list

    这更接近我的问题。

    - &GT;有趣的是, Saeb same comment和大多数答案中取得了it is not possible to use a runtime-type for generic methods 它与 List<object>

    相同

    - &GT;现在, Bryan Watts ,所有答案投票率最低,找到了一种有趣的方法来检索TRequestTResponse。他在interface / baseclass中声明了这个:

    Type DataType { get; }
    object Data { get; }
    

    这解决了如何检索变种&#39;问题。

第一个结论

由于我需要获取变体,因此我需要实现一些Type RequestTypeType ResponseType。这意味着<TRequest,TResponse>部分将毫无用处。这意味着我只使用List<Request>,而我的Stackoverflow问题的答案是:不,您无法使用/受益于在C#中存储List<Class<T>>


第二次编辑

我未能实施Bryan Watts解决方案:显然{{3}}。所以我不能写Deserialize<request.RequestDataType, request.ResponseDataType>(request)。所以我陷入了困境:如果我将我的请求添加到请求列表中,我就有了运行时类型,我再也不能使用泛型方法了。我不知道通用方法是如此不方便。  :(

第二个结论

我将有一个List<Request>,每个Request会为请求提供一个enum,然后我会打开此枚举并手动记下所有可能的请求调用:< / p>

switch (@req.RequestType)
{
    case RequestType.FirstKindOfRequest:
        await Deserialize<FirstKindOfRequest, FirstKindOfResponse>(req);
        break;
    case RequestType.SecondKindOfRequest:
        ...
}

遗憾的是,我能找到Class<T> T列表{{1}}应该是什么的唯一方法。

3 个答案:

答案 0 :(得分:2)

这是一个经典的情况。之前也问过很多次。我看到两种方法。

- &GT;显而易见的选择是拥有一个非泛型基类/接口,它可以解释您的所有请求类型,然后随身携带List<NonGenericRequest>

- &GT;第二种方法是利用协方差。我看到你面临一些问题。我将尝试一个接一个地解决它:

  

好吧,接口不能有字段。所以我必须将列表的项目强制转换为Request<>的实例才能访问我想要的内容。

不要使用字段。甚至不要想到它。切换到属性。我必须重申这一点。接口 - 实际上是所有公共API - 处理属性。

  

或者我可以在界面中设置属性。但后来它告诉我我不能使用关键字out

问题是您的媒体资源的 setters 。它打破了类型安全。有关详情,请参阅this thread

  

这意味着<TRequest,TResponse>部分将毫无用处。这意味着我只使用List,而Stackoverflow问题的答案是:不,你不能使用/受益于在C#中存储List<Class<T>>

到目前为止,您可以编写类似var requests = new List<IRequest<object, object>>()的内容。但这意味着您不会获得强类型TRequestTResponse。解决方案是摆脱泛型类型参数TRequestTResponse(即约束where T : class)并包含更可用的约束。 对您有用的东西


将它们全部结合起来,我们走了:

interface IMessage //I will call it IMessage for now
{
    //implement whatever that make deserializing possible
}

class FooRequest : IMessage
{

}

class BarResponse : IMessage
{

}

interface IRequest<out TRequest, out TResponse>
    where TRequest : IMessage 
    where TResponse : IMessage 
{
    TRequest RequestItem { get; }
    TResponse ResponseItem { get; }
    //whatever fits, but only getters for out parameter types
}

class Request<TRequest, TResponse> : IRequest<TRequest, TResponse>
    where TRequest : IMessage 
    where TResponse : IMessage 
{
    //you can extend interface properties with setters here
}

现在你可以写:

var requests = new List<IRequest<IMessage, IMessage>>();
var a = new Request<FooRequest, BarResponse>();
requests.Add(a);
foreach (var request in requests)
{
    Deserialize(request);
}

void Deserialize<TRequest, TResponse>(IRequest<TRequest, TResponse> request)
    where TRequest : IMessage
    where TResponse : IMessage
{
    //have access to request.RequestItem, request.ResponseItem etc
}

注意:为简单起见,我假设TRequstTResponse都是相似的类型,因此它们都是从IMessage演变而来的。您可以为它们分别设置IRequestIResponse类型接口。

答案 1 :(得分:1)

我认为泛型通配符可以帮助你: http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2633766-wildcard-generic

由于这种情况至少在不久的将来不会发生,我认为有三种选择:

  • 你可以投票,等待,并希望微软在仿制药中采用通配符......
  • 正如Mrida评论的那样,您可以将代码转换为使用接口,然后使用C#协方差。
  • 如果协方差不是可接受的解决方案,您可以定义仅包含非通用功能的非通用基类RequestBase,并列出RequestBase编辑:我认为这是HenkHolterman的评论)。或者,如果您不想更改Request类,则可以使用非通用功能定义非通用包装。

答案 2 :(得分:1)

您可以通过提取变体IRequest接口来利用接口差异:

interface IRequest<out T, out T1>
{
}

并在Request类中实现它:

class Request<TRequest, TResponse> : IRequest<TRequest, TResponse>
    where TRequest : class
    where TResponse : class

这将允许您创建可以存储任意List<IRequest< object, object>>个实例的IRequest

var s = new List<IRequest< object, object>>();
s.Add(new Request<c1, c2>());

如果您想要某些特定于类型的行为,您仍然必须将列表的内容强制转换为更具体的类。这可能会或可能不会影响高流量情况下的性能。

另一种选择是存储请求的字符串内容,或将它们提取到抽象基类。这个抽象类的列表对于缓存和日志记录来说已经足够了。你可以这样:

abstract class BaseRequest
{
    public string RequestContent { get; protected set; }
    public string ResponseContent { get; set; }
}

class Request<TRequest, TResponse> : BaseRequest
    where TRequest : class
    where TResponse : class

你仍然可以写:

var s = new List<BaseRequest>();
s.Add(new Request<c1, c2>());