Общие ограничения против наследования - PullRequest
0 голосов
/ 29 января 2010

Я пытаюсь написать код, чтобы помочь модульному тестированию служб WCF. Доступ к этим сервисам осуществляется через класс фасадов, который создает экземпляр прокси, затем вызывает метод прокси и возвращает результат; для каждого метода прокси. Я хотел бы иметь возможность заменить текущий код создания чем-то, что создает реальный сервис или фальшивый.

Я не мог заставить это работать. Я свел его к следующему:

using System.ServiceModel;

namespace ExpressionTrees
{
    public interface IMyContract
    {
        void Method();
    }

    public class MyClient : ClientBase<IMyContract>, IMyContract
    {
        public MyClient()
        {
        }

        public MyClient(string endpointConfigurationName)
            : base(endpointConfigurationName)
        {
        }

        public void Method()
        {
            Channel.Method();
        }
    }

    public class Test
    {
        public TClient MakeClient<TClient>()
            where TClient : ClientBase<IMyContract>, IMyContract, new()
        {
            return new MyClient("config");

            // Error:
            // Cannot convert expression of type 'ExpressionTrees.ServiceClient' to return type 'TClient'
        }
    }
}

Почему, хотя класс MyClient является производным от ClientBase<IMyContract> и реализует IMyContract, я не могу вернуть экземпляр MyClient из метода, предназначенного для возврата TClient? TClient определяет ограничение типа, которое, как я думал, означало то же самое.


Моя цель состояла в том, чтобы назвать код следующим образом:

    public void CallClient<TClient>()
        where TClient : ClientBase<IMyContract>, IMyContract
    {
        TClient client = null;
        bool success = false;
        try
        {
            client = MakeClient<TClient>();
            client.Method();
            client.Close();
            success = true;
        }
        finally
        {
            if (!success && client != null)
            {
                client.Abort();
            }
        }
    }

Но вместо того, чтобы всегда вызывать MakeClient<TClient>, я хотел, чтобы модульный тест мог вводить фиктивный объект. Поскольку приведенный выше код зависит от ClientBase<IMyContract>, IMyContract, похоже, я пытался «синтезировать» универсальный класс, который бы удовлетворял этому ограничению.

Оглядываясь назад, это не имело бы смысла. В качестве примера можно ожидать, что экземпляр ClientBase<IMyContract> будет создан таким образом, что будет создан объект Channel, который затем может делегировать метод Close.

Я решил использовать один и тот же код для реальных и поддельных сервисов. Сейчас я ввожу IMyService и вызываю IMyService.Method или client.Method в зависимости от того, является ли мое введенное свойство пустым.

Ответы [ 3 ]

8 голосов
/ 29 января 2010

В основном ваш код сводится к:

    public static T MakeFruit<T>() where T : Fruit 
    { 
        return new Apple(); 
    } 

Это всегда возвращает яблоко, даже если вы звоните MakeFruit<Banana>(). Но MakeFruit<Banana>() требуется, чтобы вернуть банан, а не яблоко.

Смысл ограничения общего типа заключается в том, что аргумент типа, предоставленный вызывающей стороной, должен соответствовать ограничению. Так что в моем примере вы можете сказать MakeFruit<Banana>(), но не MakeFruit<Tiger>(), потому что Tiger не соответствует ограничению, что T должен быть конвертируемым в Fruit. Я думаю, вы верите, что ограничение означает что-то другое; Я не уверен, что это такое.

Думайте об этом так. Формальный параметр имеет тип формального параметра. Тип формального параметра ограничивает тип выражения, используемого в качестве аргумента. Поэтому, когда вы говорите:

void M(Fruit x)

вы говорите "аргумент, переданный для формального параметра x в M, должен быть преобразован в Fruit".

Общие ограничения параметров типа точно такие же; это ограничения на то, какие аргументы типа могут быть переданы для параметров универсального типа. Когда вы говорите «где T: Fruit», это то же самое, что сказать (Fruit x) в списке формальных параметров. T должен быть типом, который идет к Fruit, так же, как аргумент для x должен быть аргументом, который идет к Fruit.

Почему вы вообще хотите использовать универсальный метод? Я не понимаю, что именно вы пытаетесь смоделировать с помощью этого метода или почему вы хотите, чтобы он был универсальным.

3 голосов
/ 29 января 2010

Вы ограничиваете TClient в части вызова MakeClient<TClient>(), а не в типе возврата.

Тип возвращаемого значения должен соответствовать типу универсального параметра, но представьте это:

public class MyOtherClient : ClientBase<IMyContract>, IMyContract
{
    public void Method()
    {
        Channel.Method();
    }
}

Это также допустимое возвращение путем вызова MakeClient<MyOtherClient>, к которому MyClient не может быть преобразовано, так как он должен возвращать тип MyOtherClient.

Обратите внимание, что изменение возврата на:

return new MyClient() as TClient;

, вероятно, обойдёт компилятор, но в моем сценарии выше во время выполнения будет нулевым.

0 голосов
/ 29 января 2010

Это должно решить вашу проблему.

static T Make<T>() where T : IConvertible
{
    var s = "";
    return (T)(Object)s;        
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...