Thoughts on the Bait and Switch PCL Trick
This blog entry started its life as comments on The Bait and Switch PCL Trick, an excellent blog entry by Paul Betts.
A little background
I've been working on a PCL for SQLite. It's on GitHub:
Concerns about the bait-and-switch approach
(I assume you've read the Paul Betts blog entry as well as the Daniel Plaisted blog entry linked therein, so I'm not going to re-describe the Bait and Switch concept from square 1.)
And then the Paul Betts blog entry showed up, which named the pattern Bait and Switch and proclaimed it to be The Right Way.
Initially, I had concerns. The technique feels fragile.
(The words "Bait and Switch" don't really contribute to this feeling (at least for me), especially after seeing Daniel Plaisted on Twitter saying that "the bait-and-switch concept is fundamental to PCLs, because mscorlib is different everywhere". Makes sense.)
But the technique still seems deserving of the word "Trick". The reference assembly (the actual PCL assembly, the one with a PCL profile) and the platform assemblies don't have much tying them together. It seems like the trick is something that just happens to work, almost by accident, and therefore might stop working later, even though I have no reason to believe that it will.
I'm setting this worry aside because the other PCL approach (dependency injection) contains a trick which feels just as fragile. The standard hack is for the PCL assembly to use reflection to locate and load and instantiate the platform assembly. This approach doesn't exactly ooze with robustness either. And in fact, the Bait and Switch technique seems to work fine on Xamarin whereas the reflection-load technique does not. So, both of these approaches might seem fragile, but one of them definitely is.
Two kinds of PCLs
If we accept the idea that the Bait and Switch pattern is The Right Way, then there are several corollaries that follow.
There are two kinds of a PCLs:
A PCL which contains only non-portable code.
A PCL which contains only portable code.
There are no PCLs which contain a mixture of the two.
(To be more precise, I am defining "portable code" as code which is compiled under a PCL profile, and "non-portable code" is code which is, er, not compiled under a PCL profile. I am not claiming that a PCL of type (1) is prohibited from using things like System.String.Length. :-) )
I find it helpful put some separation between these two very different kinds of PCLs and to explain them as follows. Perhaps others will find these explanations helpful as well.
For the purpose of this note, I will refer to PCL type (1) as a Wrapper PCL.
A Wrapper PCL is a PCL which exists to provide a portable wrapper around something that is not portable.
A Wrapper PCL always uses the Bait and Switch pattern.
We can refer to a Wrapper PCL in the singular, but it is actually multiple assemblies. There is the PCL assembly itself (the one that claims to be portable according to some profile). And there are platform assemblies, non-portable assemblies which play the role of the PCL assembly in real life scenarios.
Any consumer of a Wrapper PCL would need to reference exactly one of its assemblies. A library can reference the PCL assembly itself. An app must reference one of the platform assemblies.
With a Wrapper PCL, the PCL assembly itself exists only so that libraries can have something to reference. It is nothing more than a placeholder.
With a Wrapper PCL, the PCL assembly itself contains only stub functions. Those stub functions never get executed. They might do nothing. They might throw. They might launch nethack. Nobody knows. Nobody cares.
A Wrapper PCL, despite the presence of "Portable Class Library" in its name, contains no portable code that ever gets used.
A Wrapper PCL claims a PCL profile (such as profile 78, for example), but that profile claim is mostly just a list of which platform assemblies are supposed to be present in the package.
With a Wrapper PCL, we don't care at all about the fact that our compiler and tooling can gripe about the use of non-portable things (except of course to complain if somebody actually does try to launch nethack from a stub). There is no portable code being written, so we don't need the compiler evaluating our ability to write things that are portable.
There is no special tooling to help ensure that all of the assemblies within a Wrapper PCL actually do implement the same interface, although it's not hard to figure out ways of making this happen by code sharing across all the assemblies.
As for PCL type (2) in the list above, I will refer to this as a Feature PCL.
A Feature PCL is a library of portable code. I'll generously assume that it has some reason to exist. It therefore provides some sort of functionality. Or a feature. So I call it a Feature PCL.
A Feature PCL does not use Bait and Switch. Or Dependency Injection. It has no need for such things.
A Feature PCL is not allowed to have non-portable code in it. If it did, it would not be a PCL. Or it would violate The Right Way, so its offending non-portable portions should be abstracted out into a separate PCL using the Bait and Switch pattern.
A Feature PCL is allowed to use things that are allowed by its profile. Or it is allowed to reference other PCLs that are compatible with that profile. It is not allowed to reference anything else. The compiler and tooling help ensure this.
A Feature PCL is [conceptually] just one assembly. It might be implemented using multiple assemblies. That's fine. What is does NOT have are a set of assemblies that are designed to be alternatives.
I'm still exploring and experimenting. Feedback welcome, but my blog software doesn't support comments. I'm @eric_sink on Twitter. I'll also keep an eye on the Paul Betts blog entry for any further discussion that happens there.