强制泛型参数的类型是接口而不是特定类

时间:2012-02-06 13:55:15

标签: c# generics

鉴于这些界面和实现

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类型
  • 编译的
  • 第二行无法编译

3 个答案:

答案 0 :(得分:7)

你不能 - 没有办法将类型参数限制为接口。

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

答案 1 :(得分:2)

您的意思是将I的类型约束为IService,对吗?

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

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

如果您要求的是将参数限制为任何接口,那么,如Jon所述(但为了使我的答案不那么多余),这是非法的。< / p>

答案 2 :(得分:1)

如您所知,Register(new Service());当然会编译为Register<Service>(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<IService>(new Service());调用指定所需的界面可能是最简单和最清晰的。

修改

  

我同意Register<IService>(new Service());是最清晰的形式。但是,如何强制程序员不要忽略<IService>?例如,重新锐化可能表明<IService>是多余的。

要回答这个问题,让我们考虑一下这个问题的语义。该调用将对象(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<IValidationService, SecurityService>(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之前,这不会失败。