2012-02-23 3 views
3

Я пытаюсь скомпилировать следующий код:Литой бетонный тип общего типа?

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

Но *** линия не будет компилироваться, потому что компилятор не знает FooResponse является TResponse. Я знаю, потому что он указан в FooRequest. Есть ли способ обойти это без участия неприятного отражения (в таком случае я предпочел бы вместо этого вернуть BaseResponse)?

Спасибо.

Обновление: Я использую generics для принудительного ввода типа возврата, чтобы сайт вызова точно знал, чего ожидать. Было бы намного проще вернуть BaseResponse здесь, но это ставит бремя выяснить конкретный тип возврата вызывающему, а не обработчик запроса (который, конечно же, знает все о наборе текста).

+7

Во-первых, не следует ли вызывать метод MakeResponse, так как он возвращает TResponse? Но в более общем плане: если вам нужно проверить тип вещи и принять какое-то конкретное действие по определенному типу, то ** вы не пишете общий код в первую очередь **, так почему вы используете * generics *? Если у вас есть специальная логика, которая знает, как превратить FooRequest в FooResponse, тогда создайте метод, который принимает FooRequest и возвращает FooResponse; не требуется никаких дженериков. –

+0

@ Эрик. Я работаю над шаблоном Chain of Responsibility, в котором узлы маршрутизации просто передают сообщение вперед, и, возможно, какой-то узел посередине распознает запрос и возвращает правильный ответ. Однако я должен прояснить это в примере кода. –

+0

@forcey CoR не подразумевает ничего общего с дженериками, а совпадение по типу экземпляра не является целью дженериков. Просто пройдите по BaseRequests и используйте 'if (request is FooRequest) {...}' –

ответ

7

Как я уже сказал в своем комментарии, я подозреваю, что вы делаете это неправильно. Это похоже на злоупотребление дженериками.

Сказанное так, как вы говорите компилятору «Я знаю больше информации о типе, чем вы», путем литья.

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

Актерских к объекту, а затем TResponse сообщает компилятор «Я знаю, что есть личность, распаковка или ссылочное преобразование от ответа на TResponse». Если вы ошибаетесь, вы получите исключение во время выполнения.

+0

Спасибо - похоже, я могу отбросить FooResponse к BaseResponse, а затем TResponse. Я бы все же утверждал, что я не злоупотребляю им, хотя :) –

+1

@forcey, generics должны быть общими. Если вы выполняете кастинг при обработке общего параметра, вы, скорее всего, злоупотребляете системой. –

+0

@ RomanR. - Достаточно честно, я понимаю, что я просто использую систему вычитания типов, предоставляемую генериками, вместо того, чтобы использовать дженерики в том виде, в каком это предполагалось. –

2

На мой взгляд, вы должны получить свой BaseRequest<T> класс от не дженерика BaseRequest, а затем написать функцию:

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

Но это выглядит очень запутанным , В любом случае я позволю вам задуматься об этом

+0

Большое спасибо, это действительно очень полезно. –

Смежные вопросы