Shared Projects and C#: Best Practices
This blog post is going to go in-depth into when and how you should make use of "shared projects" in Visual Studio, when developing C# class libraries and targeting multiple .NET Framework profiles and versions.
Visual Studio 2015 introduced the concept of Shared Projects, that was intended to be introduced as a portable solution for code-sharing between projects, and as an alternative to portable class libraries (PCLs). Shared Projects function as Visual Studio or MSBuild projects that are not meant to be compiled, but are instead meant to be referenced by other application and class library projects.
Code files that are referenced by Shared Projects, are subsequently ingested by the projects that reference a Shared Project. In effect, this means that code files are included as part of a Shared Project, will compile alongside code that is featured in a Class Library or Desktop Application project.
Shared Project Structure
Shared Projects come with two separate project files that represent their appearance in Visual Studio and appearance overall.
- The "root" or "parent" project that imports or consumes the .projitems project.
- Defines all the files that are referenced and consumed by the shared project.
.NET Framework version releases are frequent, and quite often major version releases will introduce valuable features that should be integrated as part of your class library or application.
The problem is that as your shared code solution grows, and as you have an increasing amount of projects that target older versions of .NET Framework, you will begin to encounter compilation issues or warnings. Worse yet, you could find yourself in a scenario where you are duplicating code files across Class Library projects, or linking code files external to the project just so that the Class Library can compile accordingly.
Portable Class Libraries were heavily relied upon in the past as a suitable way of sharing code that was to be consumed across platforms (operating systems). It would often be the case that shareable types would be defined in a single Portable Class Library project, that would then be referenced by a platform-specific version of that Portable Class Library project (i.e. Mono on Mac and Linux, or .NET Framework on Windows), which would then provide and inject platform-specific implementations of those shareable types. Having to provide platform specific implementations for each shared type can be time-consuming and often becomes difficult to maintain as new major version releases of the .NET Framework and CLR become available.
Refer to this MSDN page about Portable Class Libraries and cross-platform development.
Code that is consumed by multiple projects is better defined as part of a shared project. Class Library projects that target varying versions of the .NET Framework can then reference the Shared Project, and build the referenced code against the .NET Framework version specified as part of the class library project settings.
Because Shared Projects in Visual Studio are still fundamentally MSBuild Projects, it still means that you can write MSBuild targets that can be shared between projects that reference your Shared Project.
Let's assume for a second that you have a class library that contains plenty of useful and shareable code. You are intending on referencing this code in multiple projects that vary in .NET Framework versions. The most optimal method of addressing this problem is by making use of one Shared Project for your class library, and having multiple (basically empty) Class Library projects reference the same Shared Project.
These empty Class Library projects can then target the version of .NET Framework than they need to, while also referencing the code that they must be compiled with. This approach prevents the needing to have platform specific implementations of shared types (interfaces) that are consumed from a portable class library. It also means that code is referenced from a single source in the solution (a Shared Project), and code files are not referenced externally.
Additionally, because a Shared Project is effectively a MSBuild project, it also means that you can share build operations (defined as part of the MSBuild DOM) amongst class library projects that reference the same Shared Project type.
- Reduces code duplication and the need for project hackery for including code files as "external items".
- Increases portability.
- Removes dependency of portable class libraries (PCLs), and the bait and switch compilation technique.
- Removes the requirement for there to be another assembly available that is referenced or consumed by the platform or framework specific assembly.