Comme vous le notez, Register(new Service());
compile bien sûr en Register<Service>(new Service());
En supposant que vous puissiez définir une logique qui choisisse l'une des interfaces implémentées du type concret comme interface à enregistrer (ce qui est une grande supposition), vous pourriez gérer les types concrets plutôt que de les faire exclure par le compilateur. Évidemment, la solution triviale consiste à exiger que le type n'implémente qu'une seule interface, mais il est peu probable que cela soit très utile.
Je pense à quelque chose de ce genre (en reprenant la suggestion de Jon Skeet et en renommant le paramètre de type) :
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
}
Tout bien considéré, il est probablement plus simple et plus clair de laisser l'appelant spécifier l'interface souhaitée avec la balise Register<IService>(new Service());
appeler.
EDIT
Je suis d'accord que Register<IService>(new Service());
est la forme la plus claire. Mais comment puis-je faire en sorte que les programmeurs n'omettent pas l'élément <IService>
? Re-sharper, par exemple, peut suggérer que <IService>
est redondant.
Pour répondre à cette question, considérons la sémantique de l'appel. L'appel associe un objet ( new Service()
) avec une interface ( IService
). Une façon de forcer les programmeurs à être explicites sur l'identité de l'interface est de faire du type un paramètre formel. En fait, si vous n'appelez aucune des méthodes de l'interface sur l'objet que vous enregistrez, vous n'avez même pas besoin de génériques :
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());
}
Cependant, même sans appeler aucun des membres de l'interface sur l'objet, il existe un autre avantage des génériques : la vérification des types au moment de la compilation. Existe-t-il un moyen d'obtenir une vérification de type à la compilation tout en obligeant le développeur à spécifier le type explicitement ? Oui : avec deux paramètres de type dans la signature de la méthode, mais un seul argument, il n'y a aucun moyen pour le compilateur de déduire les deux types, donc le développeur doit fournir les deux. C'est plus de typage, mais le code est plus explicite.
Avec cette approche, vous pouvez également contraindre le type d'implémentation au type d'interface, afin de vous assurer que le développeur n'appelle pas, par exemple, le type d'interface, 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());
}
ou même
class NewImprovedService : Service : { }
void InitializeServices()
{
Register<IService, Service>(new NewImprovedService());
}
Cela nous ramène à la question des indications de types éventuellement redondants, et l'appel est encore plus verbeux que ce que vous avez commencé, mais cela empêche le développeur d'enregistrer par inadvertance le mauvais type de service.
Nous restons cependant confrontés au problème initial, car rien n'empêche le développeur d'appeler
Register<Service, NewImprovedService>(new NewImprovedService());
Cela n'échouera pas tant qu'une vérification de l'exécution pour typeof(TServiceType).IsInterface
.