How is the intention of IServiceLocator.GetInstance(Type) different from the intention of IServiceProvider.GetService(Type)?
B

2

12

Is there a difference in intentions of the method signatures IServiceProvider.GetService(Type serviceType) and IServiceLocator.GetInstance(Type serviceType)? If so, what is the distinction?

I've always treated them as equivalent but made a choice to use a single method for consistency. This seems like a good enough solution to dealing with the two interfaces, but I'd really like to know how their usages were actually intended so that I can be sure I am using the right one in the right place. If their intention is in fact the same, then is there any reason for having multiple sets of semantics for the same purpose? (I understand that the GetInstance signature was recommended during the inception of Microsoft.Practices.ServiceLocation, but this doesn't really seem like a sound reason to introduce the duplication).

Why I'm confused

Below is a list of sometimes contradictory facts I have found in trying to find the answer to this question, as well as my interpretation thereof. I am including these so that my question can be addressed in context of all the information that is already known about this topic.

  • The MSDN documentation for IServiceProvider says that the GetService(Type serviceType) method should return

    A service object of type serviceType.
    -or-
    null if there is no service object of type serviceType.
  • The MSDN documentation for IServiceLocator lacks method documentation but the summary in the VS Object Browser of GetInstance(Type serviceType) says that the method returns "the requested service instance". However, there is also an exception entry in the documentation IServiceLocator that says that an ActivationException should be thrown if there is an error resolving the service instance.

  • ActivationException is located in the Microsoft.Practices.ServiceLocation namespace which was introduced years after IServiceProvider was introduced. So, it is understandable that the IServiceProvider does not refer to the exception. That being said, the IServiceLocator interface's documentation says nothing about returning null if no result is found. It also isn't clear whether or not the absence of an implementation of the requested service type should constitute an exception.

  • Should the absence of an implementation for a service type cause an ActivationException in IServiceLocator implementations? It doesn't look like it. The implementation template for IServiceLocator ignores any concept of a non-null post-condition.

  • The implementation template for IServiceLocator also treats IServiceProvider.GetService(Type) as alternative syntax for IServiceLocator.GetInstance(). Does this count as a violation of Liskov (due to throwing an exception in subtype that is not declared on the base type), or, would that actually require a difference in the implementation rather than the exceptions declared on the interface's method signatures? What I'm getting at is: Are we sure that the ServiceLocatorImplBase implementation template for IServiceLocator implements both interfaces correctly? Would it be a better representation of the interfaces' intentions for the IServiceProvider to wrap the GetInstance call in a try block, and return null when an exception is caught?

  • Addendum: One other issue related to this is the correspondence of IServiceLocator.GetAllInstances(Type) to IServiceLocator.GetInstance(Type). Specifically, For any type, T, should an implementation of IServiceLocator.GetAllInstances(typeof(T)) return the same result as IServiceLocator.GetInstance(typeof(IEnumerable<>).MakeGenericType(typeof(T))? (It's easy to see how this relates to the IServiceProvider correspondence, but I think it's better to keep the question simple and only compare the two methods of the same interface for this case.)

Beebe answered 13/2, 2013 at 22:6 Comment(1)
The origin of .NET IServiceProvider is COM's IServiceProvider (msdn.microsoft.com/en-us/library/cc678965(v=vs.85).aspx). It's more or less a port of the idea (consult the related COM doc for some insight on the original intent). As for the ServiceLocator one, maybe they just wanted the GetInstance overloads to have the same name. ServiceLocator is not really part of the .NET framework. It's an addin from the PAG group at Microsoft.Edge
G
12

As you already noted, the difference between IServiceProvider.GetService and IServiceLocator.GetInstance is that any IServiceProvider.GetService implementation should return null when the service isn't registered or when it can't be resolved for what ever reason, while IServiceLocator.GetInstance implementations on the other hand should throw an exception in that case (and never return null).

But note my use of the word 'should'. All the CSL adapters (for Windsor, Spring, Unity and StructureMap, etc) that are shipped with the Common Service Locator project (which owns the IServiceLocator interface) don't adhere to the IServiceProvider interface and they throw an exception when you call their IServiceProvider.GetService method.

By breaking the contract, the designers of the CSL managed to make the IServiceProvider interface utherly useless. You now simply can't rely on it to return null anymore, which is bad. Really bad. The only CSL adapter I know of that adheres to the contract is the Simple Injector adapter, but since all other implementations are broken, even this correctly implemented adapter is useless at that point, since there's no way you can safely swap implementations.

Does this count as a violation of Liskov

Absolutely. They broke the interface contract and implementations can't be substituted from one another.

The designers know about this, as can be seen from Glenn Block's comment on this thread:

Sounds like we might have messed up here. The idea of throwing an exception was an explicit design goal that we all agreed on. Making it implement IServiceProvider was more of a convenience, but sounds like this was overlooked.

The flaw has never been fixed, because the CSL has never been updated.

Guttate answered 18/2, 2013 at 11:54 Comment(4)
What should we do about it?Beebe
Complain about this on the CSL forum and add an issue. Problem is though, that fixing the bug is itself a breaking change. There's just one solution: do NOT use the CLS. Not in your LOBs, not in a reusable library.Guttate
We can surely blame all the CSL adapters, but to this day I still don't know why the clr devs at MSFT haven't adopted the java throws clause for method signatures, that would have made it explicit what the real deal for the methods were. I know the documentation is there, but most of the time people just don't care. As you said this is a Liskov violation, so at this point and given the state of the different implementation out there, do you think it is worth still using it? Commons.Logging, seems to have managed to do this just right.Escarpment
If the return-null behavior is desired, you can wrap IServiceProvider.GetService in a helper class and try-catch + return-null. The helper class would be forward compatible with any changes.Lecialecithin
R
3

I think there is a distinction between the two designs that you didn't mention:

IServiceProvider.GetService
null if there is no service object of type serviceType.

IServiceLocator.GetInstance
ActivationException should be thrown if there is an error resolving the service instance.

It is important to note that one is specifying the default case, while the other is specifying the error case. It makes sense that they would not be the same.

From the samples provided it looks like the combination is the expected implementation. null as default, wrap in ActivationException on error.

EDIT:

Specifically, For any type, T, should an implementation of IServiceLocator.GetAllInstances(typeof(T)) return the same result as IServiceLocator.GetInstance(typeof(IEnumerable<>).MakeGenericType(typeof(T))?

Wouldn't typeof(IEnumerable<T>) work as a more compact form? Additionally, why would GetInstance return anything when asked for an IEnumerable type? You could implement it that way, but I wouldn't call it automatic.

Ridinger answered 13/2, 2013 at 22:21 Comment(8)
I addressed this in my question (read the first four bullet points). Also, your inference conflicts with the provided default implementation of IServiceLocatorBeebe
@smartcaveman: That is based on your bullet points. You mention that one says what to do with no results while the other mentions what to do when it errors. No where do you say no results is an error.Ridinger
So, regarding the ActivationException vs null result, am I correct in my interpretation of your post in saying that there is no correspondence between the error case and the default case and that I should not make any assumptions about their correspondence?Beebe
@smartcaveman: Given the unification of the implementation in all the examples I would. If the implementations were distinct then I would not have made that conclusion necessarily.Ridinger
Regarding your edit, (1) typeof(IEnumerable<T>) is certainly a more compact form, but I used the reflection syntax, because I thought it made my intention more clear in the context of an interface with generic overloads. Maybe I should have written typeof(IEnumerable<>).MakeGenericType(T) instead. Either way, you clearly understood my interpretation.Beebe
(2) I understand IEnumerable<T> as an interface that exposes a single operation GetEnumerator() which constructs an IEnumerator<T> instance for the client. So, when I ask the Service Locator to GetInstance(IEnumerable<T>), I expect it to provide the default implementation of the interface that can create an IEnumerator<T>. It seems like the natural default implementation of such a service would have to be either produce an enumerator that iterates over 0 elements of the type (an empty enumerator) or an enumerator that iterates over all elements of the type.Beebe
@smartcaveman: 1. I was honestly curious, and wondered if there was a syntactic reason. 2. I always think of an empty enumerator as the default, but I only disagree about that being required, not that it would be inappropriate. (i.e. if you were using a different IServiceProvider it may not do the same)Ridinger
I think that's fair. I guess my question about that really boiled down to asking if there should be a required recursive relationship between IServiceLocator and IEnumerable<T>, which I see now is a pretty big conceptual jump to make when interpreting the interface. I think that I probably got the thought from knowing that IoC containers tend to work recursively, but that's clearly an implementation detail (not a feature of the interface).Beebe

© 2022 - 2024 — McMap. All rights reserved.