我可以有/使用一个字段/属性与列表>T可以是任意值

本文关键字:列表 Class 任意值 字段 我可以 一个 属性 | 更新日期: 2023-09-27 18:04:41

原题

我试图为我的web请求创建一个通用类

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:

    无效方差:类型参数'TRequest'必须在My.Services.IRequest<TRequest,TResponse>.RequestItem上始终有效。"请求"是协变的。

    所以我可以删除关键字out,但是当试图添加一个项目到列表时,它又告诉我:

    不能从My.Services.Request<FooRequest,BarResponse>转换为My.Services.IRequest<object,object>

  • 使用abstract class BaseRequest

    几乎相同的问题,我的基类不能有TRequestTResponse类型的字段;如何检索这两个变体?我需要对列表中的项进行强制转换。所以List<object>也一样好。

  • 使用泛型通配符

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

  • 检查c#协方差

    提供的答案太模糊,没有什么可以实现。连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# -多个泛型类型在一个列表

    这更接近我的问题了。

    ->有趣,Saeb对大多数的答案做出了与我相同的评论,这与 List<object>

    完全相同

    ->现在,Bryan Watts找到了一种有趣的方法来检索TRequestTResponse,他在所有答案中获得的票数最少。他在接口/基类中声明:

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

    解决了"如何检索变量"的问题。

第一个结论

因为我需要得到的变体,我将需要实现一些Type RequestTypeType ResponseType。这意味着<TRequest,TResponse>部分将是无用的。这意味着我将只使用List<Request>,我的Stackoverflow问题的答案是:不,你不能使用/受益于在c#中存储List<Class<T>>


第二个编辑

我在实现Bryan Watts的解决方案时失败了:显然,不可能为泛型方法使用运行时类型。所以我不能写Deserialize<request.RequestDataType, request.ResponseDataType>(request)。所以我卡住了:如果我把请求添加到请求列表中,我就有了运行时类型,我就不能再使用泛型方法了。我不知道泛型方法这么不方便。(

第二结论

我将有一个List<Request>,每个Request将为请求类型保留一个enum,然后我将打开此enum并手动写下所有可能的请求调用:

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

遗憾的是,这是唯一的方法,我可以找到持有Class<T>的列表,其中T应该是任何东西。

我可以有/使用一个字段/属性与列表<Class< >>T可以是任意值

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

->明显的选择是有一个非泛型基类/接口,它可以解释所有的请求类型,然后携带List<NonGenericRequest>

第二种方法是利用协方差。我看到你面对一些问题。我将尝试解决它,一个接一个:

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

不要使用字段。想都别想。切换到属性。我必须重申这一点。接口——实际上是所有的公共api——处理属性。

或者我可以在接口中设置属性。但是它告诉我不能使用关键字out

问题是属性的设置。它破坏了类型安全。更多信息请看这个帖子。

这意味着<TRequest,TResponse>部分将是无用的。这意味着我将只使用列表,我的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类型接口。

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

interface IRequest<out T, out T1>
{
}

并在你的Request类中实现它:

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

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

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

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

由于这至少在不久的将来不会发生,我看到三个选项:

  • 你可以投票,等待,并希望微软在泛型中采用通配符…
  • 正如Mrida所评论的,你可以将你的代码转换为使用接口,然后使用c#协方差。
  • 如果协方差不是一个可接受的解决方案,你可以定义一个非泛型基类RequestBase,只有非泛型的功能,并使RequestBase列表(编辑:我认为这是HenkHolterman的评论)。或者,如果您不想更改Request类,您可以使用非泛型功能定义非泛型包装器。