Wednesday, 27 June 2018

Xamarin PCL vs Standard Libraries

PCL's or portable class libraries are a sharing strategy that is more complex then the alternative ShareProject approach but also much more reusable, require more thought, force you to use better coding practices and are reusable between frameworks. PCL's also compile to a binary DLL, so unlike a shared project where we simply share our soucecode we instead reference the binary DLL that our PCL complies to.


However PCL's target specific platforms that you want to target and letting the IDE code to those restrictions however this results in you only being able to use a subset of features.

PCL's use Profiles to determine which apis you have access too, not all platforms share all api's so each time you add a platform to your PCL you potentials restrict the API's you can call to the intersection point of all the selected platforms. Meaning you should target as few platforms as possible.

.Net Standard libraries

With all these restrictions on PCL's in come .Net Standard libraries which create a standard way of each platform to implement it's api's meaning that you no longer get a subset based on platform.


.net standard libraries has multple versions and will continue to grow as new platforms are added and existing ones evolve. each new version is a superset of the previous versions apis

VersionAPI's
1.0Primitives, reflection, tasks, collections, LINQ to Objects, XML, etc
1.1Concurrent collections, interop, Http interactions, etc
1.2Threading timer, interop+, etc
1.3Console, File System, Trehad pool, sockets, cryptography, etc
1.4Crypto+, etc
1.5Assembly members+, streams+, etc
1.6crypto+, regex+, expressions compiling, etc
2.0Data Classes, Drawing, pipes, caching, SMTP, web sockets, serialization+ XPath, etc

Now so far it looks like the best option is to pick the latest .net standard library, well that would be nice, but negative. you have to align your .net standard library version with the version that is supported in your platform, so for example .net Framework v4.5.1 supports the .net standard v1.2 and thus you can only use the 1.2 version of .net standard at a max.

thus it comes down to a balancing act of .net standard features you want vs platforms you want to support with your library.


.NET StandardPCL Profile
1.2Profile 151 (.NET Framework 4.5.1, Windows 8.1, WP 8.1
1.2Profile 44 (.NET Framework 4.5.1, Windows 8.1)
1.1Profile 111 (.NET Framework 4.5, Windows 8, WP8.1)
1.1Profile 7 (.NET Framework 4.5, Windows 8)
1.0Profile 259 (.NET Framework 4.5, Windows 8, WP8.1, WP 8)
1.0Profile 78 (.NET Framework 4.5, Windows 8, WP 8)

The chart above depicts two way compatibility, meaning that in .net std 1.2 we can use a pcl profile 151 and vice versa.

Because PCL's and .net standard libraries are shared at the binary level, many of the platform specific API's can't be added to the project directly. There are strategies to mitigate this restriction such as:

Leverage platform specific api's in platform projects, then pass the results to your library.

using System;

namespace pav.pclExample {
    class Person {
        public string FullName { get; set; }
        public DateTime BirthDate { get; set; }
        public Func<Person, int> GetAge { get; set; }

        public string DispalyPerson()
        {
            int age = -1;
            if (GetAge != null && (age = GetAge(this)) > -1)
                return $"{FullName} is {age} years old";
            return $"{FullName} was born {BirthDate.ToShortDateString()}";
        }
    }

    class Program {
        static void Main(string[] args) {
            var getAgeFunc = new Func<Person, int>(p => {
                var today = DateTime.Today;
                var age = today.Year - p.BirthDate.Year;
                if (p.BirthDate > today.AddYears(-age))
                    age--;
                return age;
            });

            var p1 = new Person {
                FullName = "Pawel Ciucias",
                BirthDate = new DateTime(1984, 1, 31)
            };

            var p2 = new Person {
                FullName = "Magda Tywoniuk",
                BirthDate = new DateTime(1984, 6, 28),
                GetAge = getAgeFunc
            };

            Console.WriteLine(p1.DispalyPerson());
            Console.WriteLine(p2.DispalyPerson());
        }
    }

}

now in the above i didn't bother making a separate class library project, and adding a reference to it. Instead i just included the class inside the same console application, the principle however stays the same; I created a GetAge property in the Person class, then inside my application i define a func to calculate age and when creating instances of my Person class i simply pass that func in. In this way i could use a Platform Specific api like get the GPS location of the device, then pass that location to my Class library.

In a way the above itself is a kind of Dependency injection, however we'd have to pass our func to each instance of our Person class, but take a look at the following

using System;

namespace pav.pclExample {
    class Person {
        public string FullName { get; set; }
        public DateTime BirthDate { get; set; }
        static Func<Person, int> GetAge { get; set; }

        public Person() { }
        public Person(Func<Person, int> getAge) : this() => GetAge = getAge;

        public string DispalyPerson()
        {
            int age = -1;
            if (GetAge != null && (age = GetAge(this)) > -1)
                return $"{FullName} is {age} years old";
            return $"{FullName} was born {BirthDate.ToShortDateString()}";
        }
    }

    class Program {
        static void Main(string[] args) {
            var getAgeFunc = new Func<Person, int>(p => {
                var today = DateTime.Today;
                var age = today.Year - p.BirthDate.Year;
                if (p.BirthDate > today.AddYears(-age))
                    age--;
                return age;
            });

            var p1 = new Person(getAgeFunc) {
                FullName = "Pawel Ciucias",
                BirthDate = new DateTime(1984, 1, 31)
            };

            var p2 = new Person {
                FullName = "Magda Tywoniuk",
                BirthDate = new DateTime(1984, 6, 28)
            };

            Console.WriteLine(p1.DispalyPerson());
            Console.WriteLine(p2.DispalyPerson());
        }
    }

}

what we did was create a private static func to calculate age, and then we created a constructor that let's us pass in a definition for our GetAge func, and thus we only have to pass it in once and all of our Person classes will be able to use that same static implementation.


This is starting to feel more and more like a modern code sharing strategy and not just a haphazard hack.