如您所述,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>?例如,Re-sharper 可能表明<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 之前不会失败。