A Sharedproject shares all of it's content with each individual target project, meaning that everything inside your shared project is copied into your iOS, Droid and UWP projects at compile time. This means that the code written in your sharedproject will become part of your Droid, iOS and UWP projects and then complied using that target projects compilation settings. Sharedprojects do not have an output of their own, there is not dll or exe, they just become part of the projects that they are linked to.
This will work just fine if you have no platform independent code, but for anything but the most trivial apps this will not be the case and we need to come up with some code sharing strategies.
Conditional compilation
The simplest method is Conditional compilation, which is the use of preprocessor directives to decide which code gets compiled in which project.Symbol | Project |
---|---|
#if __MOBILE__ |
Any mobile project (vs. desktop) |
#if __ANDROID__ |
Xamarin.Android - defined by the compiler |
#if __IOS__ |
Xamarin.iOS - defined by the compiler |
#if __MAC__ |
Xamarin.Mac - defined by the compiler |
#if __TVOS__ |
iOS tvOS - defined by the compiler |
#if __WATCHOS__ |
iOS watchOS - defined by the compiler |
#if WINDOWS_UWP |
Windows 10 UWP - defined in build settings |
These conditional compilation symbols are defined in the project settings and need to be define for both DEBUG and RELEASE Builds separately.
- The advantage of this method is that it's fairly simple and doesn't take too much planning or thinking about to leverage
- The disadvantage is that it becomes very difficult to maintain as platform independent code grows.
- This is the appropriate approach when you have very little Platform-specific code
Class Mirroring
Another Code sharing strategy in Shareprojects is Class Mirroring, class mirroring let's you overwrite code in your shared project with code inside your platform specific project, let's take a look at the following Xamarin form
<?xml
version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="pav.SharedProject00.MainPage">
<StackLayout>
<Button x:Name="MyButton" Text="click me"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"/>
</StackLayout>
</ContentPage>
using pav.SharedProject00.Services;
using System;
using Xamarin.Forms;
namespace pav.SharedProject00
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
MyButton.Clicked +=
MyButton_Clicked;
}
private void
MyButton_Clicked(object sender,
EventArgs e)
{
var ms = new
MessageService();
ms.Show("Hello world", "This is my message");
}
}
}
we simply create an event handler for the clicked event on our button, in this handler we call the show method on our MessageService, so let's take a look at that.
namespace pav.SharedProject00.Services
{
public class MessageService
{
public void Show(string title, string message) => Popup.ShowPopup(title,
message);
}
}
now our message simply calls our static ShowPopup method on our Popup class, but where is that define or i should where are they defined, because each project get's their own implementation of ShowPopup, first let's take a look at our android implementation
Droid
using Android.App;
using Android.Text;
using Android.Widget;
namespace pav.SharedProject00.Services
{
internal class Popup
{
internal static void ShowPopup(string title, string message)
{
var toast = Html.FromHtml($"<big><b>{title}</b></big>: {message}");
Toast.MakeText(Application.Context,
toast, ToastLength.Short).Show();
}
}
}
And now let's take a look at our UWP implementation
UWP
using Windows.UI.Popups;
namespace pav.SharedProject00.Services
{
internal class Popup
{
internal static void ShowPopup(string title, string message)
{
var dialog = new
MessageDialog(message,title);
dialog.ShowAsync();
}
}
}
notice that both implementation are different however they both have the same namespace and the same signature, this allows us to specify our platform specific code in the independent projects
Partial Classes & Methods
A third option for sharing code in a Sharedproject is to use partial classes and methods; this approach let's you define part of your class inside your sharedproeject and the other part inside your platform specific project.
But first let's take a look at the following console application to get a feel for partial classes.
But first let's take a look at the following console application to get a feel for partial classes.
using System;
namespace Pav. ConsoleSharedOne
{
partial class Person {
public DateTime BirthDate { get; set; }
public bool Male { get; set; }
partial void
displayPerson();
}
partial class Person {
public string
FirstName { get; set; }
public string LastName
{ get; set; }
public int GetAge()
{
var today = DateTime.Today;
var age = today.Year - BirthDate.Year;
if (BirthDate > today.AddYears(-age))
age--;
return age;
}
partial void
displayPerson()
{
var prefix = Male ? "Mr." : "Ms.";
var msg = $"{prefix} {FirstName} {LastName} is {GetAge()} years old";
Console.WriteLine(msg);
}
public void
DisplayPerson() => displayPerson();
}
class Program {
static void Main(string[] args) {
var p1 = new Person {
Male = true,
FirstName = "Pawel",
LastName = "Ciucias",
BirthDate = new DateTime(1984, 1, 31)
};
var p2 = new Person {
Male = false,
FirstName = "Magda",
LastName = "Tywoniuk",
BirthDate = new DateTime(1984, 6, 28)
};
p1.DisplayPerson();
p2.DisplayPerson();
}
}
}
now as the name implies partial classes let us break our logic out into multiple class declarations, now above i define both parts of the Person class in the same file, but i could have just as easily separated it into two different files.
PersonOne.cs
using System;
namespace Pav.PartialClassExample
{
partial class Person
{
public DateTime BirthDate { get; set; }
public bool Male { get; set; }
partial void
displayPerson();
}
}
PersonTwo.cs
using System;
namespace Pav.PartialClassExample
{
partial class Person
{
public string
FirstName { get; set; }
public string LastName
{ get; set; }
public int GetAge()
{
var today = DateTime.Today;
var age = today.Year - BirthDate.Year;
if (BirthDate > today.AddYears(-age))
age--;
return age;
}
partial void
displayPerson()
{
var prefix = Male ? "Mr." : "Ms.";
var msg = $"{prefix} {FirstName} {LastName} is {GetAge()} years old";
Console.WriteLine(msg);
}
public void
DisplayPerson() => displayPerson();
}
}
Main.cs
using System;
using Pav.PartialClassExample;
namespace Pav.ConsoleSharedOne{
class Program {
static void Main(string[] args) {
var p1 = new Person {
Male = true,
FirstName = "Pawel", LastName = "Ciucias",
BirthDate = new DateTime(1984, 1, 31)
};
var p2 = new Person {
Male = false,
FirstName = "Magda", LastName = "Tywoniuk",
BirthDate = new DateTime(1984, 6, 28)
};
p1.DisplayPerson();
p2.DisplayPerson();
}
}
}
now some caveats
- All parts of a partial class have to be within the same namespace, they can be in different files, but must share the same namespace.
- partial methods cannot have an accessor such as [public, private, etc]
- it goes without saying but partial methods must be void
So how does this apply to SharedProjects?
First add another console application to your solution
Next add a Shared project to your solution
Then add references from both your console applications to your Shared project
now once that's done move your PersonOne.cs file into your shared project.
create a different implementation of PersonTwo.cs in your second console application.
You should have something like the above, notice that both console applications have implementations for PersonTwo.cs and share the implementation for PersonOne.cs
Let's take a look at our new implementation of PersonTwo.cs
using System;
namespace Pav.PartialClassExample
{
partial class Person
{
public string
FirstName { get; set; }
public string LastName
{ get; set; }
public bool Married
{ get; set; }
public int GetAge()
{
var today = DateTime.Today;
var age = today.Year - BirthDate.Year;
if (BirthDate > today.AddYears(-age))
age--;
return age;
}
partial void
displayPerson()
{
string prefix;
if (Male)
prefix = "Mr.";
else if (Married)
prefix = "Mrs.";
else
prefix = "Miss.";
var msg = $"{prefix} {LastName} is {GetAge()} years old";
Console.WriteLine(msg);
}
public void
DisplayPerson() => displayPerson();
}
}
It's a slight change in the displayPerson() method but the results are different.
Now you may be wondering ok, well that's fine and dandy, but why don't i just accomplish the same thing with inheritance, I could define a person class inside my shared project and then inherit that implementation in my platform specific projects and override methods that need to be overridden and so one; and the answer is complexity.
In a complex project inheritance is more likely along the lines you want to head, but that's with the assumption that your Sharedproject is the Business layer, but what if it's the View layers as well? Finally move your program file from one of your console apps into your shared project and delete it from the other so that your solution looks like the following.
now based on whichever console project you execute you'll leverage a different version of the PersonTwo.cs file, but both console applications will share PersonOne.cs and Program.cs. Remember that the content of the shared project is complied inside of each console application, so you can think of the above solution as.