In part 1 of this series I showed that PCLs may not be a “one size fits all” solution to target all .NET subsets. You may need to use shared projects and file links. To maximize code reuse in these other projects, we end up having to manage code that is specific to .NET subsets. Most people use preprocessor directives, a method inherited from C/C++, but the result may be something like this:

namespace NetFabric
{
#if NET35 || UNITY
[Serializable]
#endif
  public struct Angle
#if !MF_FRAMEWORK
    : IEquatable<Angle>
    , IComparable
    , IComparable<Angle>
    , IFormattable
#endif
  {
#if UNITY
  [SerializeField]
#endif
  double radians;
  }
}

Many people finds this messy and confusing.

Partial classes and structs

C# lets you split the declaration of a class, struct, interface or method into multiple source files. The compiler merges and compiles these as if they’ve always been one single file. This makes it possible to split the platform specific code into separate files. We just have to add to each project only the files containing the portions that are relevant to it.

The NetFabric.Angle project uses this method and splits the Angle struct definition into the following files:

  • Angle.cs – containing the parts common to all platforms.
  • Angle.Net.cs – containing the parts common to .NET 3.5 and the PCL projects.
  • Angle.Net35.cs – containing the parts specific to .NET 3.5.
  • Angle.NetMF.cs – containing the parts specific to .NET Micro Framework.
  • Angle.Unity.cs – containing the parts specific to Unity.

Check out these files and notice that all files declare a public partial struct Angle but each one has its own set of attributes and interface implementations.

 

Partial methods

It’s possible that the same method may have different signatures on different platforms. For example, the constructor for ArgumentOutOfRangeException doesn’t have the paramValue argument when on .NET Micro Framework.

Using the partial keyword on a method makes it possible to have a method with different implementations on different platforms. This can be used to manage the different signatures of a method.

First, declare in the class or structure that will make the call, the signature of a new method that contains all the arguments, and replace the previous method call by a call to this new method:

 static partial void ThrowArgumentOutOfRange(string paramName, object paramValue, string message);

 public static Angle FromDegrees(int degrees, double minutes)
 {
 if (minutes &lt; 0.0 || minutes &gt;= 60.0)
  ThrowArgumentOutOfRange(&quot;minutes&quot;, minutes, &quot;Argument must be positive and less than 60.&quot;);

 ...
 }

Then, on each platform-specific code, declare the body of the method.

For .NET 3.5 and above, pass all the arguments to the constructor:

 static partial void ThrowArgumentOutOfRange(string paramName, object paramValue, string message)
 {
   throw new ArgumentOutOfRangeException(paramName, paramValue, message);
 }

While for .NET Micro Framework the paramValue argument is ignored:

 static partial void ThrowArgumentOutOfRange(string paramName, object paramValue, string message)
 {
   throw new ArgumentOutOfRangeException(paramName, message);
 }

The partial keyword makes the method optional. No compile-time or run-time errors will result if the method is called but not implemented. If you want to make it mandatory, just remove the signature declaration and the partial keywords from the platform specific code.

Conclusion

The use of the partial keyword in shared projects is a clean and easy to maintain solution for handling platform specific code.
Featured image: “Spaceship Earth” by aalmada

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s