通用约束与继承

Generic Constraints vs. Inheritance

提问人:John Saunders 提问时间:1/29/2010 最后编辑:John Saunders 更新时间:4/29/2012 访问量:1803

问:

我正在尝试编写一些代码来帮助对 WCF 服务进行单元测试。这些服务是通过 Facade 类访问的,该类创建代理实例,然后调用代理方法并返回结果;对于每种代理方法。我希望能够用创建真实服务或虚假服务的东西替换当前的创建代码。

我无法让它起作用。我把它归结为以下几点:

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'
        }
    }
}

为什么即使类派生自并实现,我也不能从旨在返回 的方法返回实例? 指定一个类型约束,我认为这意味着同样的事情。MyClientClientBase<IMyContract>IMyContractMyClientTClientTClient


我的目标是像这样调用代码:

    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>ChannelClose

我最终决定为真实和虚假服务运行完全相同的代码。我现在正在注入一个 ,并调用或取决于我注入的属性是否为 null。IMyServiceIMyService.Methodclient.Method

C# WCF 泛型模拟

评论

1赞 John Saunders 12/21/2013
我仍然无法想象为什么这个问题会招致反对票。我希望其中一位反对者能告诉我,这样我就可以改进这个问题。

答:

3赞 Nick Craver 1/29/2010 #1

您限制了调用部分的 TClient,而不是返回类型。MakeClient<TClient>()

返回类型必须与泛型参数的类型匹配,但请想象一下:

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

这也是通过调用 的有效返回,它不可转换为,因为它应该返回 .MakeClient<MyOtherClient>MyClientMyOtherClient

请注意,将返回值更改为:

return new MyClient() as TClient;

可能会让它通过编译器,但在我上面的场景中在运行时为 null。

0赞 ChaosPandion 1/29/2010 #2

这应该可以解决您的问题。

static T Make<T>() where T : IConvertible
{
    var s = "";
    return (T)(Object)s;        
}

评论

0赞 John Saunders 1/29/2010
很遗憾没有。我真的需要一个参数化的构造函数。我更新了我的问题以表明这一点。
8赞 Eric Lippert 1/29/2010 #3

基本上,您的代码可以归结为:

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

这始终返回一个苹果,即使您调用 .但需要归还香蕉,而不是苹果。MakeFruit<Banana>()MakeFruit<Banana>()

泛型类型约束的含义是调用方提供的类型参数必须与约束匹配。因此,在我的示例中,您可以说但不是因为 Tiger 不符合 T 必须可转换为 Fruit 的约束。我想你认为约束意味着别的东西;我不确定那是什么。MakeFruit<Banana>()MakeFruit<Tiger>()

可以这样想。形式参数具有形式参数类型。形式参数类型限制用作参数的表达式的类型。所以当你说:

void M(Fruit x)

你是说“在 M 中为形式参数 x 传递的参数必须可转换为 Fruit”。

泛型类型参数约束完全相同;它们限制了可以为泛型类型参数传递哪些类型参数。当你说“where T : Fruit”时,这与在形式参数列表中说(Fruit x)是一样的。T 必须是指向 Fruit 的类型,就像 x 的参数必须是指向 Fruit 的参数一样。

为什么你一开始就想有一个通用方法?我不明白你到底想用这种方法建模什么,或者你为什么希望它是通用的。

评论

0赞 John Saunders 1/29/2010
埃里克,我会把这个例子更深入地讲,这样我就清楚了我想做什么。