【问题标题】:Forcing type of a generic parameter be an interface instead of a specific class强制泛型参数的类型是接口而不是特定类
【发布时间】:2012-02-27 23:43:57
【问题描述】:

鉴于这些接口和实现

interface IService {}
class Service : IService {}

使用通用方法

void Register<I>(I service)
{
    var type = typeof(I);
}

如何使以下几行关于泛型类型保持一致?

Register<IService>(new Service())    // type is 'IService'
Register(new Service());             // type is 'Service'

两个选项都可以接受:

  • 这两行都是使用IServie 类型编译的
  • 第二行编译失败

【问题讨论】:

标签: c# generics


【解决方案1】:

您的意思是将I 的类型限制为IService,对吧?

使用the where clause to constrain泛型类型:

void Register<TServiceType>(TServiceType service)
    where TServiceType : IService

如果您要的是将参数限制为 any 接口,那么,正如 Jon 所说(但试图使我的答案不那么多余),这是非法的。

【讨论】:

  • 这将允许所有实现接口的类型
  • 我认为您误解了这个问题。我相信这个想法是将I 限制为一个接口——任何接口——而不是一个具体的类。
  • @JonSkeet 我的误解可能是有道理的。我会暂时保留这个,直到我们发现其他情况,以防万一。但你可能是对的。如果是这样,我不是你想要做的。
【解决方案2】:

您不能 - 无法将类型参数限制为接口。

当然,您可以执行执行时间检查,如果I 不是接口,则抛出异常。 (顺便说一句,命名约定建议这应该是T,而不是I。)

【讨论】:

  • @sil:不确定 - 我似乎记得另一个问题是使用代码合同询问这类事情,但它不起作用 - 但我可能错了。
【解决方案3】:

如您所述,Register(new Service()); 当然会编译为 Register&lt;Service&gt;(new Service());

假设您可以定义一些逻辑来选择具体类型的实现接口之一作为注册接口(这是一个很大的假设),您可以处理具体类型而不是让编译器排除它们。显然,简单的解决方案是要求该类型只实现一个接口,但这不太可能很有用。

我正在考虑这些方面的事情(接受 Jon Skeet 的建议并重命名类型参数):

void Register<T>(T service) 
{ 
    var type = typeof(T); 
    if (type.IsInterface)
    {
        Register(type);
        return;
    }

    var interfaceType = ChooseTheAppropriateInterface(type);
    Register(interfaceType);
} 

void Register(Type typeToRegister)
{
    //...
}

Type ChooseTheAppropriateInterface(Type concreteType)
{
    var interfaces = concreteType.GetInterfaces();
    //... some logic to pick and return the interface to register
}

考虑到所有因素,让调用者通过Register&lt;IService&gt;(new Service()); 调用指定所需的接口可能是最简单和最清楚的。

编辑

我同意Register&lt;IService&gt;(new Service()); 是最清晰的形式。但是我怎样才能强制程序员不要省略&lt;IService&gt;?例如,Re-sharper 可能表明&lt;IService&gt; 是多余的。

要回答这个问题,让我们考虑一下调用的语义。该调用将对象 (new Service()) 与接口 (IService) 相关联。强制程序员明确接口标识的一种方法是使类型成为形式参数。事实上,如果你没有在你注册的对象上调用任何接口的方法,你甚至不需要泛型:

void Register(Type serviceType, object service)
{
    // ... some argument validation

    if (!(serviceType.IsAssignableFrom(service.GetType())))
        throw...

    // ... register logic
}

//usage:
void InitializeServices()
{
    Register(typeof(IService), new Service());
}

然而,即使不调用对象上的任何接口成员,泛型还有另一个好处:编译时类型检查。有没有办法在强制开发人员明确指定类型的同时进行编译时类型检查?是的:方法签名中有两个类型参数,但只有一个参数,编译器无法推断这两种类型,因此开发人员必须同时提供这两种类型。输入更多,但代码更明确。

通过这种方法,您还可以将实现类型限制为接口类型,以确保开发人员不会调用,例如Register&lt;IValidationService, SecurityService&gt;(new SecurityService());

interface IServiceBase { } // interface that all service interfaces must implement; might not be needed
interface IService : IServiceBase { }
class Service : IService : { }

void Register<TServiceType, TImplementingObject>(TImplementingObject service)
    where TServiceType : IServiceBase  // superfluous if there's no IServiceBase, of course
    where TImplementingObject : TServiceType
{
    // ... implementation
}

//usage:
void InitializeServices()
{
    Register<IService, Service>(new Service());
}

甚至

class NewImprovedService : Service : { }

void InitializeServices()
{
    Register<IService, Service>(new NewImprovedService());
}

这让我们回到了一些可能是冗余的类型指示的问题上,并且调用比您开始时更加冗长,但它确实可以防止开发人员无意中注册错误的服务类型。

然而,我们仍然遇到了最初的问题,因为没有什么能阻止开发人员调用

Register<Service, NewImprovedService>(new NewImprovedService());

在运行时检查 typeof(TServiceType).IsInterface 之前不会失败。

【讨论】:

  • 我同意Register&lt;IService&gt;(new Service()); 是最清晰的形式。但是我怎样才能强制程序员不要省略&lt;IService&gt;?例如,Re-sharper 可能表明&lt;IService&gt; 是多余的。
  • @ReuvenBass 说得好。有关更多想法,请参阅编辑后的答案。
猜你喜欢
  • 1970-01-01
  • 2013-08-16
  • 1970-01-01
  • 1970-01-01
  • 2011-10-31
  • 1970-01-01
  • 2014-11-23
  • 1970-01-01
  • 2021-10-11
相关资源
最近更新 更多