Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling missing types in PCL with real types existing on some of the platforms

This may be a question answered somewhere, but I can't seem to find an answer.

I am working on a project to port Mono.Data.Sqlite to Windows Phone and Windows Store, but of course this requires System.Data to be ported as well. I decided to use PCLs as this removes the need for separate assemblies for the platforms (I am also checking out Silverlight, but this is not a priority) In doing this, I have managed to get most of the functionality across except for one type - DBNull :(

This is where the problem is, WP and SL have the DBNull declared, but WinRT does not. Is it possible to do something in the single assembly to use the native DBNull on the platforms where it exists (WP and SL) and use a custom implementation on WinRT? I haven't seemed to find a way to do this. I have looked at other solutions: (a) create a PCL for SL and WP and exclude the DBNull type and another assembly for WinRT, or (b) create a single assembly that references a custom assembly for WP and SL with type forwarding to the native implementation of DBNull, and an assembly for RT with the implementation of DBNull.

Is there any other ways, or which one is better?

like image 991
Matthew Avatar asked Jan 25 '14 22:01

Matthew


2 Answers

You can solve this by using strong named assemblies and the TypeForwardedToAttribute.

First, give your PCL assembly a strong name by signing it, preferably by adding an AssemblyKeyFileAttribute to the AssemblyInfo.cs file. More details are found here.

Next, add a simple DBNull class with sufficient contents. Here is an example, modify it to your own content:

namespace System
{
  public sealed class DBNull
  {
    public static readonly DBNull Value = new DBNull();
    private DBNull() { }
  }
}

Now, you will need to create target specific assemblies with the same strong name and version as the PCL assembly. These assemblies will replace the PCL assembly in the target specific applications making use of DBNull. If this should work, it is absolutely necessary that the strong name and version are the same. If the signing file is referenced in the AssemblyInfo.cs file of the PCL project, one efficient approach is simply to add (with a link) this file to the target specific project instead of the auto-generated AssemblyInfo.cs file. Also, in the project settings, make sure that the assembly name is the same for the target specific and PCL libraries.

In the .NET Framework and Windows Phone libraries replacing the PCL library, you should now add a .cs file, for example called TypeForwarding.cs, with the following contents:

using System;
using System.Runtime.CompilerServices;

[assembly: TypeForwardedTo(typeof(DBNull))]

When this assembly is referenced (replacing the PCL assembly), the compiler will then know that it should look for the existing DBNull implementation in the target framework.

On the other hand, in the Windows Store (WinRT) library replacing the PCL, you should include the same DBNull source file that you included in the PCL library, since WinRT does not contain a pre-existing implementation of DBNull.

EDIT

First of all, a very important note! If you are using TypeForwardedToAttribute, the method or property signature in the PCL assembly must be exactly the same as the signature in the target specific consumer assembly. Otherwise the method/property will not be properly identified in the end-user application. I usually consult the MSDN documentation to obtain the correct signatures.

It also crucial that if you are using type forwarding for enum types, the numeric values of the enums must be equal to the target specific implementations. A resource that is very helpful when identifying these values is the Mono source code.

Whether you should use non-functional stubs in your PCL assembly varies from type to type, and as far as I know there is no specific support for generating consumer project assemblies from a PCL.

For example, there are some types that are available on the three targets .NET, Windows Store and Windows Phone 8, but not in PCL, like the Marshal and GCHandle classes. In this case you would provide unimplemented methods in your PCL like this:

public static class Marshal
{
  public static IntPtr AllocHGlobal(int cb) {
    throw new NotImplementedException();
  }
}

and then use type forwarding in all your consumer libraries:

[assembly: TypeForwardedTo(typeof(Marshal))]

On the other hand, there are types that are available on some but not all of the targets, and not in PCL.

In that case, if it is appropriate, you can include a working implementation in the PCL (as suggested in the original answer) and include that same implementation in the consumer project for the target that is missing this type.

If it impractical or impossible to provide a PCL implementation that works with any target, you could instead include a non-functional stub in the PCL, and provide a target specific implementation in the consumer projects.

like image 170
Anders Gustafsson Avatar answered Nov 15 '22 07:11

Anders Gustafsson


While Anders approach is technically sound (and is exactly how we implemented the "modern" surface area of .NET Core) it is not free of challenges.

If you're only using this approach for libraries that you reuse from your own applications then it can work very well. However, if you want to ship your component to 3rd parties, for example, via NuGet then this approach doesn't scale as the ecosystem has to agree on what is the one representation on the platforms that don't include the type already. That's why we keep shipping portability packs, such Microsoft.Bcl.Async where we do that work for the community.

System.Data is on our radar, in particular System.Data.Common so that we can enable 3rd party SQL providers, such as SQLite, to share the abstractions and common utilities (DBNull) across all platforms. We don't have an ETA for that work, but it's certainly on our TODO list.

like image 41
Immo Landwerth Avatar answered Nov 15 '22 07:11

Immo Landwerth