How to Make Portable Class Libraries Work for You
A Portable Class Library is a .NET library that can be used (in binary form, without recompiling) on multiple .NET platforms. When you create a Portable Class Library in Visual Studio, you can choose which platforms to target. Portable libraries support targeting the .NET Framework, Silverlight, Windows Phone, Windows Store apps, and XBox 360 XNA games.
Portable Class Library creation dialog in Visual Studio 2012 |
This can be a big improvement over creating a separate project file for each platform you want to target, and keeping them all in sync. For example, shown below is the project structure for the MVVM Light Toolkit.
MVVM Light Toolkit Project Structure |
I bet it’s not fun to keep all those projects in sync each time a file needs to be added, removed, or renamed. There are only three logical libraries here (the core library, extras, and the tests). Wouldn’t it be great to just have one portable version of each of those libraries instead of seven different ones?
Trouble in Paradise
Unfortunately, it’s not that simple. The fact that Portable Class Libraries need to run on multiple platforms imposes some constraints on what you can do in them. Many .NET Framework APIs (such as File I/O or anything to do with the user interface) aren’t available. Portable libraries can’t reference non-portable libraries. You can’t use P/Invoke from most portable libraries. This is all because portable libraries are supposed to actually run on different platforms, but it means it can be more complicated to write them, and can in some cases be very difficult (or not possible) to convert existing .NET libraries to portable libraries.
We’ve seen lots of people excited about portable libraries, but we’ve also seen some people who have run into these limitations questioning whether portable libraries are useful for them. In this post, I’ll cover how you can use Portable Class Libraries even when you run into these limitations. First, let’s look at the reasons some APIs aren’t supported, and what functionality actually is supported.
Why APIs Aren’t Portable
Here are some of the reasons APIs aren’t available in portable libraries (taken from David Kean’s MSDN article on Creating a Continuous Client Using Portable Class Libraries):
The API Isn’t Implemented by All Platforms
Traditional .NET Framework file IOs, such as System.IO.File and System.IO.Directory, fall into this bucket. Silverlight and Windows Phone use the System.IO.IsolatedStorage APIs (though different from the .NET Framework version), whereas Windows Store apps use Windows.Storage.
The API Isn’t Compatible Across All Platforms
Some APIs look and feel the same, but it’s hard or impossible to write code against them in a portable and consistent way. ThreadStaticAttribute, which enables static fields to have a unique value for each thread, is an example. Though it’s present on both the Windows Phone and Xbox platforms, neither of their runtimes supports it.
The API Is Considered Obsolete or Legacy
These APIs either contain behavior that’s unlikely to be present on future platforms, or they’ve been replaced by newer technologies. BackgroundWorker is an example of this; it was replaced by Task and the new asynchronous program features in Visual Studio 2012.
We Ran out of Time
Most APIs weren’t written with portability in mind. We spend a significant amount of time going through each API to make sure it can be programmed against in a portable manner. This might involve tweaking or adding to the API to make it portable. Because of the time and effort involved, in the first version of PCLs we made available on Visual Studio Gallery, we prioritized the high-value, highly used APIs. System.Xml.Linq.dll and System.ComponentModel.DataAnnotations.dll are examples of APIs that weren’t available in that first version but are now available in the Visual Studio 2012 release.
What Is Supported in Portable Libraries?
For a .NET Framework API to be available in a portable library, the API needs to be supported on all the platforms the portable library is targeting. This means that the platforms you choose to target in your portable library affect the APIs that are available to you. In general, the more platforms you choose to support, the fewer APIs will be available to you. For example, the Task type from the TPL is available in .NET 4 and up, Silverlight 5, and Windows Store apps. So if you target only those platforms you will be able to use the Task type in your portable library. If you target Silverlight 4, XBox, or Windows Phone 7, you won’t be able to use it.
The following chart gives a summary of what API areas are available in portable libraries and on what platforms.
Portable Class Library Platform/Feature Support Matrix |
The full list of APIs available in portable libraries as of Visual Studio 2012 RTM is available in this spreadsheet: Portable Class Library APIs
Solving Problems With Indirection
Any problem in computer science can be solved with another layer of indirection. – David Wheeler (probably)
What should you do when you’re trying to write a portable library but you need some functionality that isn’t supported? You can’t call the API directly, and you can’t reference a library that does, because portable libraries can’t reference non-portable libraries. The solution is to create an abstraction in your portable library that provides the functionality you need, and to implement that abstraction for each platform your portable library targets. For example, if you need to save and load text files, you might use an interface like this:
public interface IFileStorage { Task SaveFileAsync( string filename, string contents); Task<String> LoadFileAsync( string filename); } |
It’s a good idea to include only the functionality you need in the abstraction. In this example, the interface doesn’t abstract general file system concepts such as streams, folders, or enumerating files. This makes the abstraction more portable and easier to implement. The methods return Tasks so that the implementation for Windows Store apps can call the WinRT file IO APIs, which are async.
Creating an abstraction allows portable libraries to call into non-portable code, and this pattern is applicable almost any time you need to access non-portable functionality from a portable library. Of course, you need some way for the portable code to get a reference to an implementation of the abstraction. How you do that can depend on whether you are writing a cross platform app or a general purpose reusable library.
Cross Platform Apps
When writing a cross platform app in .NET, you should create the user interface separately for each platform to take advantage of the screen sizes and UI metaphors of each platform. To avoid duplicating work, you’d like most of the rest of your code to be shared across platforms. A clean separation between your user interface and the rest of your code will make this easier, and a great pattern for this is the Model-View-View Model (MVVM) pattern.
Diagram of the MVVM Pattern |
Using the MVVM pattern, you can share models and view models across platforms using Portable Class Libraries. When a model or view model needs to access non-portable functionality, you can create an abstraction for that functionality and implement it in platform-specific code. The app for each platform will contain the views and reference the portable library, as shown in the following diagram.
Cross Platform App Project Structure with Portable Class Libraries and MVVM |
A simple example of a cross platform app that uses portable libraries and MVVM can be found in thisPortableNotepad sample. In that app, the view model needs access to an instance of IFileStorage, so it takes it as a constructor parameter. The view’s constructor creates the view model, passing in the platform-specific implementation of IFileStorage, and sets the view’s DataContext to the view model it created.
For more complicated apps, it can get messy passing the implementations for the platform specific functionality down into portable code. One solution to this is a simple “service locator” in the portable library with static properties corresponding to the portable abstractions, which get set during app startup. Portable code can then access the non-portable functionality through the service locator class.
public class ServiceLocator { public static IFileStorage FileStorage { get ; set ; } public static IPhotoChooser PhotoChooser { get ; set ; } } |
Of course, using static properties like this may raise “singleton pattern” alarm bells, and will make it harder to write unit tests for your code. So you may want to use an IoC container, as hooking up dependencies while maintaining testability and separation of concerns is what IoC containers are designed to do. Autofacis one IoC container which has a portable version, and there’s also a portable fork of Ninject.
David Kean’s MSDN article on Creating a Continuous Client Using Portable Class Libraries includes a sample which demonstrates a more complex cross platform app which uses portable libraries and MVVM. The sample is a shopping list app called OnYourWayHome and has a Windows Phone 7 and a Windows Store app version. It shows how you can handle navigation between pages, use an IoC container, and use Azure Service bus to sync state between multiple clients. The version from the MSDN article is based on the VS 2012 Beta and Windows 8 Consumer Preview, so it will need some updates to run on Windows 8 RTM. An updated version of the app which uses MEF Lightweight composition (instead of Autofac) is included as a sample in the MEF source code.
Another sample app is Contoso Helpdesk, which I’ve been using in presentations about Portable Class Libraries. This app uses the same architecture (and much of the same code) as the OnYourWayHome sample. Like OnYourWayHome, it has a Windows Phone 7 and Windows Store app version, but instead of a shopping list, it keeps track of helpdesk tickets. In Contoso Helpdesk, I’ve factored out the MVVM infrastructure pieces which could be reused in other apps into a library called MvvmPortable. You can use this library as a starting point to write your own cross platform apps.
Reusable Portable Libraries
If you are creating a library for others to use (such as an open source library), you probably want to make it as easy as possible to reference and use your library. That means consumers of the library should be able to add a reference to it as a single unit. Some libraries won’t need any non-portable functionality, so you can create a single portable DLL. Autofac and Json.NET are two libraries which have done this. For libraries that need to be factored into multiple DLLs (for example a portable and a platform specific part), publishing them as NuGet packages allows consumers to reference the different components of the library as one unit. (NuGet also makes it a lot easier to consume a library that consists of a single assembly, so either way I recommend publishing your libraries on NuGet.)
The code to use your library should also be simple. That means the project referencing your library shouldn’t have to manage hooking up the platform specific part of your library with the portable part. One way to do this is to have an initialize method in your platform specific part of your library which passes references to platform specific implementations down to the portable part of your library. Here’s an example of what this could look like:
public class MyLibraryPhoneUtil { public static void Initialize() { MyLibraryServiceLocator.FileStorage = new PhoneFileStorage(); MyLibraryServiceLocator.PhotoChooser = new PhonePhotoChooser(); } } |
This is simple for you as a library author to write, but it means that consumers of your library need to call the initialize method on startup.
For consumers of your library, it will be easier if the portable part of your library connects to the platform specific part with no action required on their part. You can do this by having the portable part load the platform-specific part by calling Assembly.Load(), and using reflection to get the functionality it needs. For an example of how to do this, see the Portable Class Libraries Contrib project. The code which handles this functionality is in the Source\Portable.Runtime\Adaptation folder. The Reactive Extensions (Rx) also use this method to connect their portable System.Reactive.Core assembly with the platform-specific System.Reactive.PlatformServices assembly. Speaking of Reactive Extensions, the team’s blog posts announcing Rx 2.0 Beta, RC, and RTM cover how they converted Rx to support Portable Class Libraries, and are a great case study for how to do so with a large existing library.
Another option is to use reflection to call platform-specific APIs from a portable library directly. This can be a good choice for a library which can be almost entirely portable, but which has a small, isolated set of platform-specific functionality it needs to access. This portable fork of MVVM Light uses this method in the ViewModelBase class to determine whether it is running in the designer.
Doing the Impossible
Portable libraries can’t reference non-portable libraries, because anything a portable library references needs to run on the same platforms the portable library supports. But what if there is a library that is supported on all the platforms a portable library needs, it just has a separate DLL for each platform instead of being implemented as a Portable Class Library? Wouldn’t it be nice if you could reference that library from a portable library? With a bit of work, you can.
To use a concrete example, let’s imagine there’s a .NET library for sqlite that you would like to use from a portable library. The trick is to reference one assembly when compiling, and deploy a different one to be used at runtime. You can create a portable library with the same API surface and assembly identity as the sqlite library. This is your “reference assembly.” This assembly should never be used at runtime, so you don’t actually have to implement anything—just throw NotImplementedExceptions in all methods. Then a portable library can reference and compile against the reference assembly version of the sqlite library. Apps that use the portable library can reference the normal implementation of the sqlite library for the platform they target, which will cause that version of the sqlite library to be packaged with the app and used at runtime.
I don’t know of any projects that are currently using this strategy, and I’m not sure if overall it’s likely to be a better solution than creating a portable abstraction like I described previously. Still, it’s an interesting option. I’d love to hear how it works for you if you do try using it. If it turns out to be a useful pattern we can consider providing more tooling to support it.
Conclusion
Using the patterns I’ve described, Portable Class Libraries can be used for a wide variety of projects, both for cross platform apps and general purpose reusable libraries. Of course, the fact that these patterns are needed in the first place means that using portable libraries can be more complicated than writing code for a single platform. If you want to target multiple platforms, however, you will have to deal with additional complexity somehow. You can choose to keep entirely separate code bases; share your code with linked source files, separate projects for each platform, and conditional compilation; or use portable libraries to share code between platforms. There are advantages and disadvantages to each of these options. I think in many cases, portable libraries are a great choice—they avoid the project proliferation problem and make it easier to share code in a way that will be maintainable in the long run.
Sample of how Portable Class Libraries can simplify the solution structure for a cross platform app –MvvmCross |
The functionality supported in portable libraries will continue to grow over time. At the beginning of 2011, we released the first version of Portable Class Library support as an add-in for Visual Studio 2010 with support for a fairly limited set of APIs. With the Visual Studio 2012 release, we now support much more functionality (which is also now available in the add-in for Visual Studio 2010). We will continue to work on making more code portable across more platforms using portable libraries. We also hope to see third-party libraries which provide portable abstractions and implementations for you so you have less work to do when writing a cross-platform app (Sqlite and Xamarin.Mobile would be great candidates for this).
We’d also love to hear your feedback. We hope you will try using Portable Class Libraries and let us know what problems you run into and what seems to be more difficult than it should be. This will help us know what to focus on for future improvements. You can leave a comment on this blog post or contact David Kean and me on Twitter.
For more information on Portable Class Libraries, see this Overview of Portable Class Libraries on the .NET Framework Blog, and the links from this Channel 9 Visual Studio Toolbox show on Portable Class Libraries.
How to Make Portable Class Libraries Work for You的更多相关文章
- Inside Portable Class Libraries
Portable Class Libraries were introduced with Visual Studio 2010 SP1 to aid writing libraries that c ...
- Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用
通过本文你将学会如下内容: 1,如何使用Xamarin开发跨平台(Windows,Android,iOS)应用. 2,如何使用微软的登录界面登入Microsoft账号. 3,如何使用Outlook邮箱 ...
- The .NET of Tomorrow
Ed Charbeneau(http://developer.telerik.com/featured/the-net-of-tomorrow/) Exciting times lie ahead f ...
- DotNet 资源大全中文版(Awesome最新版)
Awesome系列的.Net资源整理.awesome-dotnet是由quozd发起和维护.内容包括:编译器.压缩.应用框架.应用模板.加密.数据库.反编译.IDE.日志.风格指南等. 算法与数据结构 ...
- .NET Core 2.0版本预计于2017年春季发布
英文原文: NET Core 2.0 Planned for Spring 2017 微软项目经理 Immo Landwerth 公布了即将推出的 .NET Core 2.0 版本的细节,该版本预计于 ...
- Xamarin.Forms 简介
An Introduction to Xamarin.Forms 来源:http://developer.xamarin.com/guides/cross-platform/xamarin-forms ...
- A Complete List of .NET Open Source Developer Projects
http://scottge.net/2015/07/08/a-complete-list-of-net-open-source-developer-projects/?utm_source=tuic ...
- 介绍.NET 开发必备工具 .NET Portability Analyzer
随着.NET的原来越开放,不仅仅是开源这么简单了,也意味着.NET程序员要关注越来越多的平台,涵盖.NET Mic Framework, Xamarin,Mono,.NET等等,从windows到li ...
- .Net 跨平台可移植类库正在进行
[原文发表地址] Cross-Platform Portable Class Libraries with .NET are Happening [译文发表地址] .Net 跨平台可移植类库正在进行 ...
随机推荐
- C#基础系列 - 抽象类及其方法的学习
在C#中使用关键字 abstract 来定义抽象类和抽象方法. 不能初始化的类被叫做抽象类,它们只提供部分实现,但是另一个类可以继承它并且能创建它们的实例. "一个包含一个或多个纯虚函数的类 ...
- CCF CSP 201403-2 窗口
CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201403-2 窗口 问题描述 在某图形操作系统中,有 N 个窗口,每个窗口都是一个两边与坐标 ...
- python 字符串截断
>>> s = '%20and%201=2%20union%20select%201,group_concat%28table_name%29,3,4,5,6,7,8,9,10,11 ...
- linux kernel.shmall shemax shemin解释
Linux X86-64操作系统,Oracle 10g数据库,由8G加到16G,把kernel.shmmax参数改到17179869184(16G)后,发现只要修改sga_max_size和s ...
- VMware下CenOS7系统的安装及lnmp服务器的搭建
CentOS7系统的安装 CentOS7下载:http://mirrors.aliyun.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1708.iso 下 ...
- 关于mysql中storage_engine中 MYISAM 和 INNODB 的选择
简单点说 读操作多用myisam 写操作多用innodb 不过现在大家好像基本都用innodb,本人小白一个就直接用InnoDB. MySQL自20多年前成立以来一直支持可插拔存储引擎,但在一段相当长 ...
- html中元素的id和name的区别(2016-1-22)
HTML中元素的Id和Name属性区别 一直以来一直以为在html中,name和id没什么区别,今天遇到一个坑才发现(PHP获取不到表单数据,原因:元素没有name,只定义了id),这两者差别还是很大 ...
- 【我要学python】面向对象系统学习
第一节:初识类的定义和调用 c1.py #类 = 面向对象 #类 最基本作用:封装 #类中不仅可以定义变量 还可以定义函数等等,例: class student( ): name = ' ' age ...
- 浅析SDWebImage
浅析SDWebImage 在日常的开发过程中,如果去优雅的访问网络的图片并去管理每个工程必须要面对的问题,如果想要在工程里面提供易用.简洁.方便管理的解决方案还是很有挑战的,毕竟还要兼顾图片文件的缓存 ...
- [USACO5.5]Hidden Password
题目大意: 求字符串最小表示. 思路: 本来按照lbn187的课件,知道SAM可以求字符串最小表示. 然而他并没有提供例题,就自己找了一道做. 大体思想就是把字符串复制一遍接在后面,构建SAM,然后每 ...