Могу ли я иметь/использовать поле/свойство со списком‹Class‹T››, где T может быть чем угодно?

Оригинальный вопрос

Я попытался создать общий класс для своих веб-запросов.

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

Типы TRequest и TResponse используются для сериализации и десериализации с помощью 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).


Первое редактирование

После множества комментариев, пары неудовлетворительных ответов и двойного голосования я рассмотрю ниже то, что я пробовал на основе всего этого:

  • #P10# #P11# #P12#
    #P13#
    #P14#
    #P15#
  • Использование abstract class BaseRequest

    Почти такая же проблема, мой базовый класс не может иметь поле типа TRequest или TResponse; как получить два варианта? Мне нужно бросить элемент списка. Так что List<object> было бы так же хорошо.

  • Использование универсальных подстановочных знаков

    Не существует и, вероятно, никогда не будет.

  • Проверка ковариации C#

    Слишком расплывчатый ответ дан и ничего для его достижения. Нет даже ссылки на MSDN. Я явно говорю о List<>, который является ковариантным типом. Решить этот вопрос не так просто, как man google.

  • Проверка цепочки: общий список анонимных классов

    Абсолютно не дубликат, так как вопросы/ответы не предназначены для хранения в типизированном поле/свойстве:

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

    Это просто List<Request<TRequest, TResponse>> и, к сожалению, я не могу объявить что-либо с типом var в классе вне метода.

  • #P25# #P26# <блочная цитата> #P27#
  • Проверка потока: C# — несколько универсальных типов в одном список

    Это становится ближе к моей проблеме.

    -> Интересно, Saeb сделал тот же комментарий, что и у меня относительно большинства ответов с все то же самое, что и List<object>

    -> Теперь Брайан Уоттс, набравший наименьшее количество голосов из всех ответов, нашел интересный способ получить TRequest и TResponse. Он объявил это в интерфейсе/базовом классе:

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

    Это решает проблему «как получить варианты».

Первый вывод

Поскольку мне нужно будет получить варианты, мне нужно будет реализовать некоторые Type RequestType и Type ResponseType. Это означает, что часть <TRequest,TResponse> будет бесполезна. Это означает, что я буду использовать только List<Request>, а ответ на мой вопрос Stackoverflow: нет, вы не можете использовать/выгодно хранить List<Class<T>> в C#.


Второе редактирование

Мне не удалось реализовать решение Брайана Уоттса: по-видимому, невозможно использовать тип времени выполнения для универсальных методов. Поэтому я не могу написать Deserialize<request.RequestDataType, request.ResponseDataType>(request). Итак, я застрял: если я добавляю свои запросы в список запросов, у меня есть типы времени выполнения, и я больше не могу использовать общие методы. Я не знал, что универсальные методы настолько неудобны. :(

Второй вывод

У меня будет List<Request>, и каждый Request будет содержать enum для типа запроса, затем я включу это перечисление и вручную запишу все возможные вызовы запроса:

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

К сожалению, это единственный способ, которым я смог найти список Class<T>, где T должно было быть чем угодно.


person Cœur    schedule 23.05.2014    source источник
comment
См. общий список анонимных классов.   -  person huMpty duMpty    schedule 23.05.2014
comment
Что ты пытаешься сделать? Почему вам нужен строго типизированный запрос, но слабо типизированный список запросов? Какую изначальную проблему вы пытаетесь решить?   -  person Panagiotis Kanavos    schedule 23.05.2014
comment
@PanagiotisKanavos все, чего я хочу достичь, уже написано в вопросе, если бы вы могли внимательно его прочитать: веб-запросы с типами для сериализации, и мне нужен список веб-запросов для постановки в очередь, кэширования и обработки ошибок. ^_^   -  person Cœur    schedule 23.05.2014
comment
Превратите new Request<Foo, Bar>() в new Request(Foo, Bar).   -  person Henk Holterman    schedule 23.05.2014
comment
@huMptyduMpty, который все равно создаст список одного класса. Здесь не хотят, а хотят.   -  person Dennis_E    schedule 23.05.2014
comment
@HenkHolterman, это, вероятно, даже не скомпилируется.   -  person rosko    schedule 23.05.2014
comment
@mrida да, это может быть проблемой. Хорошая точка зрения.   -  person rosko    schedule 23.05.2014
comment
Если вы позволите Request<TRequest, TResponse> реализовать некоторый интерфейс, вы можете составить список этого типа интерфейса. Список строго типизирован, поэтому то, что вы хотите, невозможно. Request<T1, T2> отличается от Request<T3, T4> типа   -  person Dennis_E    schedule 23.05.2014
comment
@rosko - вы можете скомпилировать его, изменив class Request{...}   -  person Henk Holterman    schedule 23.05.2014
comment
@HenkHolterman, если вы измените его на class Request{...}, то да, вы правы, но вы не включили это изменение в свой комментарий.   -  person rosko    schedule 23.05.2014
comment
Я проголосовал за открытие этого вопроса. Те, которые связаны, не являются близкими дубликатами.   -  person nawfal    schedule 28.06.2014
comment
Я не совсем понимаю, что вы говорите о начале «Позднего издания». Но ваша проблема может быть здесь: Или я мог установить свойства в интерфейсе. Что вы хотите сделать, так это иметь свойства в interface иметь только get, а не set. Принимая во внимание, что реализующее свойство в классе может иметь как get, так и set   -  person Ben Aaronson    schedule 30.06.2014


Ответы (3)


Это классическая ситуация. Раньше тоже много раз спрашивал. Я вижу два подхода.

-> Очевидным выбором было бы иметь необщий базовый класс/интерфейс, который может учитывать все ваши типы запросов, а затем использовать List<NonGenericRequest>.

-> Второй подход будет использовать ковариацию. Я вижу, вы столкнулись с некоторыми проблемами. Я попробую решить ее, одну за другой:

Ну, интерфейсы не могут иметь поля. Поэтому мне пришлось бы привести элемент списка к экземпляру Request<>, чтобы получить доступ к тому, что я хочу.

Не используйте поля. Даже не думай об этом. Переключитесь на свойства. Я должен повторить мысль. Интерфейсы — фактически все общедоступные API — имеют дело со свойствами.

Или я мог бы установить свойства в интерфейсе. Но затем он говорит мне, что я не могу использовать ключевое слово out

Проблема заключается в установщиках ваших свойств. Это нарушает безопасность типов. См. этот поток для более.

Это означает, что часть <TRequest,TResponse> будет бесполезна. Это означает, что я буду использовать только список, и ответ на мой вопрос Stackoverflow: нет, вы не можете использовать/выгодно хранить List<Class<T>> в C#.

К настоящему времени вы можете написать что-то вроде var requests = new List<IRequest<object, object>>(). Но это означает, что вы не получаете строго типизированные TRequest и TResponse. Решение состоит в том, чтобы избавиться от аргументов универсального типа TRequest и TResponse (т. е. вашего ограничения 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
}

Примечание. Для простоты я предположил, что TRequst и TResponse являются похожими типами, и, следовательно, оба они произошли от IMessage. Для них можно использовать отдельные интерфейсы типа IRequest и IResponse.

person nawfal    schedule 05.07.2014
comment
Спасибо. Я рассмотрю это через несколько дней. PS: TRequst->TRequest. - person Cœur; 08.07.2014
comment
Без вопросов. Что означает TRequst-›TRequest? - person nawfal; 08.07.2014
comment
ctrl+f TRequst и вы увидите ^_^ - person Cœur; 08.07.2014

Вы можете воспользоваться разнообразием интерфейса, извлекая вариант IRequest интерфейса:

interface IRequest<out T, out T1>
{
}

и реализуйте его в своем классе запросов:

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>());
person Panagiotis Kanavos    schedule 23.05.2014

Я думаю, что общие подстановочные знаки могут помочь вам в этом: http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2633766-wildcard-generic

Так как этого не произойдет по крайней мере в ближайшее время, я вижу три варианта:

  • Вы можете голосовать, ждать и надеяться, что Microsoft примет подстановочные знаки в дженериках...
  • Как прокомментировал Мрида, вы можете преобразовать свой код для использования интерфейсов, а затем использовать ковариантность С#.
  • Если ковариация не является приемлемым решением, вы можете определить необщий базовый класс RequestBase только с неуниверсальной функциональностью и составить список RequestBase (изменить: я думаю, что это комментарий Хенка Холтермана). ). В качестве альтернативы, если вы не хотите изменять класс Request, вы можете определить неуниверсальную оболочку с неуниверсальной функциональностью.
person MaMazav    schedule 23.05.2014
comment
Я не понимаю, как можно использовать подстановочные знаки таким образом, чтобы обеспечить безопасность типов во время компиляции. - person Ben Aaronson; 30.06.2014
comment
Что именно вы имеете в виду, когда говорите о безопасности типов во время компиляции? Я думаю, вы не говорите, что Java не сохраняет безопасность типов. - person MaMazav; 01.07.2014
comment
Как вы предлагаете использовать подстановочные знаки здесь, если они были включены в С#? Насколько мне известно, отсутствие подстановочных знаков не ограничивает функциональность в C#, потому что вместо этого вы можете просто сделать класс или метод универсальным. - person Ben Aaronson; 02.07.2014
comment
Тот факт, что вы можете обойти отсутствие подстановочных знаков с помощью дженериков, не означает, что подстановочные знаки тоже не являются хорошим решением... В частности, если вы посмотрите на первую часть вопроса (единственную, которая существовала, когда мой ответ был написан) вы увидите, что его вопрос: есть ли способ иметь типизированный список смешанных типизированных запросов. Wildcards приходит именно для решения таких проблем. Обратите внимание, что он собирается использовать его для целей сериализации, для которых у вас нет приведения и безопасности типов. - person MaMazav; 02.07.2014