7
votes

I'm trying to build a Portable Class Library which uses implementations from the platform when available. For example, Lazy<T> is available on .NET 4.5, Windows Store Apps, Windows Phone 8, BUT it's not available on Windows Phone 7, Silverlight 4. When my PCL is loaded on one of the platforms that has a Lazy<T> implementation, I want to use the platform's implementation. When it's not available on the platform, I want to use my own implementation. It seems to be possible because the Microsoft BCL is doing it, but I haven't figured out how to implement it.

I've read that by using the TypeForwardedToAttribute, you can redirect the PCL to use the implementation from the platform. I'm not quite sure how to configure my Visual Studio Projects to achieve this result. If CoreLib is my library, and ShimLib contains my implementation of Lazy<T>. Where do I add the TypeForwardedToAttribute? The attribute requires an actual Type reference typeof(System.Lazy<>), which doesn't work when Windows Phone 7 is targeted in the PCL. If I remove Windows Phone 7, then I can't add a reference from CoreLib to ShimLib because ShimLib doesn't support all the platforms that CoreLib does. How do I handle this?

Yes, I know Lazy<T> is super easy to implement, but it's just an example, and my actual situation applies to many more classes that are less trivial to implement.

2
[TypeForwardedTo] does not actually solve the underlying deployment problem. In the case of PCL, it is Microsoft that takes care of deploying the assembly that may or may not contain the [TypeForwardedTo], based on the platform. You won't get the same help for your own forwarded class.Hans Passant
Lazy<T> is "super easy to implement" if you disregard LazyThreadSafetyMode. Don't worry I got you covered: Theraot's Lazy<T> for .NET 3.5 or previous For an alternative see LazyNeedle<T> (please report any bugs). full disclosure: Yes, I'm the same Threaot.Theraot

2 Answers

8
votes

The way Microsoft.Bcl does this by shipping two assemblies with the same identity; one with the type itself, and one with the type forward. You reference the one with the type when targeting platforms that don't support Lazy (include portable library combinations that include one of these platforms). And reference the one with the type-forward when targeting platforms with Lazy, this enables to consume libraries built against the older platforms.

Make note, Microsoft.Bcl has a little advantage that you don't have. We ship an assembly that has the same identity as one that is already in later versions, which means that when a Windows Phone 7 app runs on Windows Phone 8, they get the version that's built in, not the one in the shim library. You are unable to mimic that, but that's probably something you can live with in your situation.

5
votes

The principle idea of type forwarding is very well explained in this question and this article and I'm not repeating the details here. In summary, however, the idea is to be able to reuse a library A without recompiling, even though it references a library B that is being replaced by library C. To that end, library B has to be modified such that it forwards the reference to library C, which is exactly what the TypeForwardedTo attribute does.

How does this help you? Well, you could create your ShimLib such that it is referenced by all your projects, but uses conditional compilation and type forwarding to link it to the framework libraries if they exist. Fortunately, someone already did that for you :)

Edit in response to Matt's comment: You're right, I overlooked that Theraot doesn't fall back to the original implementation. And I guess that will be very hard to achieve without recompiling. The best you can do is probably to follow this strategy, i.e. to have different build configurations for the different framework versions. That way, you can conditionally compile the TypeForwardedToAttribute. At least, this saves you from having to duplicate code. If you really don't want to distribute different versions of your code, you might implement a bootstraper that determines the framework version and loads the version of your assembly that has been compiled using the build configuration for this version