C# Porting Strategies
This is going to be a quick guide for porting a C# codebase to different platforms.
Wait, what?
Isn’t C# portable on its own?
No – there are two aspects to C# – the language itself and the target platform. Even though .NET is ostensibly the target, the question is what flavor? .NET running on a desktop machine or a server? Windows Mobile? Silverlight? Mono?
All of these are going to present some interesting issues on how to create a unified code base that is easy to maintain.
Here’s a challenge: VisualStudo doesn’t support conditional assembly references. Project files do, but the UI doesn’t this means that in order to work with different assembly references you need to have different projects for each target set of assemblies. This can be confusing in that the same files will be a part of several different projects.
To start, what you should do is create a project for each target platform and add the appropriate references. Then you should create a set of conditional compilation symbols that represent your targets as well as symbols that represent certain features you want to expose (or not).
Strategy #1 – Use Preprocessor Symbols
I don’t like doing this – it hurts readability, but it gets you going quickly. If you find yourself doing something like this:
#if HAS_SERIALIZABLE
[Serializable]
#endif
#if HAS_FORMS
public Form GenerateForm()
#else
public AdaptedForm GenerateForm()
#endif
{
// ...
}
then you’re doing something wrong – you should make a goal to use the preprocessor symbols as little as possible – and when you do they should be around the largest unit of code that you can – hopefully an entire file.
Strategy #2 – Refactor Classes
You can do this by putting as much platform-neutral functionality into a base class and then have each target platform define the actual concrete class for that platform:
public class WidgetBase {
public WidgetBase() { }
public int Weight { get; set; }
}
#if TARGET_SILVERLIGHT
public class Widget : WidgetBase { }
#endif
#if TARGET_DESKTOP
[Serializable]
public class Widget : WidgetBase, IDrawable {
public Draw(Graphics g) { /*...*/ }
}
#endif
Strategy #3 – Partial Classes
Generally speaking, I don’t like partial classes. I think that when I start dividing up my classes into partials, that the class is too big and needs to be refactored. However, you can do some nice things in partial classes. Partial classes merge all attributes from all classes as well as all interfaces, so we can revisit the sample in the first strategy and clean it up.
// main file
public partial class SomeUIElement
{
// ... implementation
}
// serializable file
#if HAS_SERIALIZATION
using System.Runtme.Serialization;
[Serializable]
public partial class SomeUIElement
{
// .... more implementation
}
#endif
// GDI file
#if HAS_GDI
using System.Drawing;
public partial class SomeUIElement
{
void Draw(Graphics g) { /* ... */ }
void Draw(Bitmap bm) { /* ... */ }
}
#endif
Strategy #4 – Type Aliasing
This is useful when you have a large establish code base and you are moving it to Silverlight where you discover that that you don’t have System.Drawing.Rectangle (at least not in the same form).
using System;
#if !SILVERLIGHT
using System.Drawing;
#else
using System.Windows;
using Rectangle = System.Windows.Rect;
#endif
namespace MyNameSpace {
public class MyUIElement {
public Rectangle CalculateBounds() { /* ... */ }
}
}
This now lets me use my existing Rectangle definitions as long as Rect is more or less compatible.
Strategy #5 – Neutral Interfacing and Abstract Factories
This is standard OOP practice – you define a neutral interface that defines how an object behaves and then use a factory to make the appropriate object.