57

I have a class library with all my database logic. My DAL/BLL.

I have a few web projects which will use the same database and classes, so I thought it was a good idea to abstract the data layer into its own project.

However, when it comes to adding functionality to classes for certain projects I want to add methods to certain classes.

For example, my data layer has Product and SomeItem objects:

// Data Access Layer project

namespace DAL {
  public class Product { 
     //implementation here 
  }

  public class SomeItem {
     //implementation here 
  }
}

In one project I want to add an interface that is used by different content items, so I have a class called:

// This is in Web Project
namespace DAL {
  public partial class Product : ICustomBehaviour {

    #region ICustomBehaviour Implementation
       TheSharedMethod();
    #endregion
  }
}

Is it a good idea to write a partial class in a separate project (creating a dependency) using the same namespace? If it's a bad idea, how can I get this type of functionality to work?

It doesn't seem to want to merge them at compile time, so I'm not sure what I'm doing wrong.

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
Armstrongest
  • 15,181
  • 13
  • 67
  • 106

8 Answers8

98

You can't write a partial class across projects. A partial class is a compile-time-only piece of syntactic sugar - the whole type ends up in a single assembly, i.e. one project.

(Your original DAL file would have to declare the class to be partial as well, by the way.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • So, then can i add "Interface" functionality to a class in another project? – Armstrongest Nov 21 '08 at 19:49
  • 2
    @Kent: Not at 8 o'clock in the evening, no :) – Jon Skeet Nov 21 '08 at 19:50
  • 5
    @Atomiton: You can't change the existing class from a different project, no. You *could* derive from the existing class and also implement the interface. That has some downsides though - most notably that you have to make sure that you always create an instance of your new type not the base one. – Jon Skeet Nov 21 '08 at 19:51
  • @Kent Boogart: I'm with you. It's rare that I come across an interesting question that Jon Skeet hasn't commented on. – Armstrongest Nov 21 '08 at 19:51
  • @Jon: Yeah. I went down the route and ran into that limitation. I may end up doing that, though. – Armstrongest Nov 21 '08 at 19:52
  • Can we declare a class as partial, but then never supply another part for it (in the same project)? If yes, what is the design principle behind the decision for allowing it? Why not have the default behaviour as such for "class" keyword? – Sabuncu Feb 11 '12 at 11:38
  • 1
    @Sabuncu: Yes, that's possible although rarely useful. I don't think I'd want it as the default behaviour as that would suggest that there's *probably* another part to look for. – Jon Skeet Feb 11 '12 at 11:41
  • Adding the class as a linked file will allow you to extend a partial class across an assembly. You cant use this technique if you don't have access to the original source though. – Amir Feb 12 '12 at 16:00
  • 2
    @Amir: It's not *really* across an assembly. It's just including the same source code in two assemblies, which isn't the same thing. – Jon Skeet Feb 12 '12 at 16:02
  • @JonSkeet yep, completely understand :-) – Amir Feb 18 '12 at 13:38
  • This is apparently "more possible" now with the advent of Shared Projects, though this seems pretty much like the "linked file" approach with a more convenient mechanism. – anton.burger Jun 23 '16 at 09:20
  • Down voting since it is now possible thanks to Shared Projects. – FanManPro May 10 '19 at 03:32
15

Using Visual Studio 2015 and later it is possible to split partial classes across projects: use shared projects (see also this MSDN blog).

For my situation I required the following:

  • A class library that defines some classes used as interface by several client applications.
  • The client application.
  • A setup application that creates tables in the database.
    Due to limitations of the installer this setup has to be self contained. It cannot reference any assemblies beyond the .NET framework. The setup should insert some enum constants into tables, so ideally it should reference the class library.
    The setup can import a Shared Project.
  • As shared project is similar to copy pasting code, so I want to move as little as possible into the shared project.

The following example demonstrates how partial classes and shared projects allow splitting classes over different projects.

In Class Library, Address.cs:

namespace SharedPartialCodeTryout.DataTypes
{
    public partial class Address
    {
        public Address(string name, int number, Direction dir)
        {
            this.Name = name;
            this.Number = number;
            this.Dir = dir;
        }

        public string Name { get; }
        public int Number { get; }
        public Direction Dir { get; }
    }
}

Class Library is a normal Visual Studio Class Library. It imports the SharedProject, beyond that its .csproj contains nothing special:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
<!-- standard Visual Studio stuff removed -->
    <OutputType>Library</OutputType>
<!-- standard Visual Studio stuff removed -->
  </PropertyGroup>
<!-- standard Visual Studio stuff removed -->
  <ItemGroup>
    <Reference Include="System" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Address.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="..\SharedProject\SharedProject.projitems" Label="Shared" />
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Address.Direction is implemented in the SharedProject:

namespace SharedPartialCodeTryout.DataTypes
{
    public partial class Address
    {
        public enum Direction
        {
            NORTH,
            EAST,
            SOUTH,
            WEST
        }
    }
}

SharedProject.shproj is:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Label="Globals">
    <ProjectGuid>33b08987-4e14-48cb-ac3a-dacbb7814b0f</ProjectGuid>
    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
  </PropertyGroup>
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
  <PropertyGroup />
  <Import Project="SharedProject.projitems" Label="Shared" />
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

And its .projitems is:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
    <HasSharedItems>true</HasSharedItems>
    <SharedGUID>33b08987-4e14-48cb-ac3a-dacbb7814b0f</SharedGUID>
  </PropertyGroup>
  <PropertyGroup Label="Configuration">
    <Import_RootNamespace>SharedProject</Import_RootNamespace>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="$(MSBuildThisFileDirectory)Address.Direction.cs" />
  </ItemGroup>
</Project>

The regular client uses Address including Address.Direction:

using SharedPartialCodeTryout.DataTypes;
using System;

namespace SharedPartialCodeTryout.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create an Address
            Address op = new Address("Kasper", 5297879, Address.Direction.NORTH);
            // Use it
            Console.WriteLine($"Addr: ({op.Name}, {op.Number}, {op.Dir}");
        }
    }
}

The regular client csproj references the Class Library and not SharedProject:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
<!-- Removed standard Visual Studio Exe project stuff -->
    <OutputType>Exe</OutputType>
<!-- Removed standard Visual Studio Exe project stuff -->
  </PropertyGroup>
<!-- Removed standard Visual Studio Exe project stuff -->
  <ItemGroup>
    <Reference Include="System" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\SharedPartialCodeTryout.DataTypes\SharedPartialCodeTryout.DataTypes.csproj">
      <Project>{7383254d-bd80-4552-81f8-a723ce384198}</Project>
      <Name>SharedPartialCodeTryout.DataTypes</Name>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

The DbSetup uses only the enums:

The DbSetup.csproj does not reference Class Library; it only imports SharedProject:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
<!-- Removed standard Visual Studio Exe project stuff -->
    <OutputType>Exe</OutputType>
<!-- Removed standard Visual Studio Exe project stuff -->
  <?PropertyGroup>
<!-- Removed standard Visual Studio Exe project stuff -->
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="Microsoft.CSharp" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Program.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="App.config" />
  </ItemGroup>
  <Import Project="..\SharedProject\SharedProject.projitems" Label="Shared" />
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

To conclude:

Can you split a partial class across projects?

Yes, use Visual Studio's Shared Projects.

Is it a good idea to write a partial class in a separate project (creating a dependency) using the same namespace?

Often not (see the other answers); in some situations and if you know what you are doing it can be handy.

Kasper van den Berg
  • 8,951
  • 4
  • 48
  • 70
  • For the GUI lovers out there: Adding a shared code project as a reference via the Visual Studio UI fails sometimes because Visual Studio simply doesn't show the option. Just edit the *.csproj file like in the post and it will work. – nkr Jun 20 '18 at 14:29
  • This answer needs to climb the ranks since the accepted one is no longer true. Let's pull together folks! – FanManPro May 10 '19 at 03:29
5

I can't answer your question about the best way to organize your layers, but I can try to answer your question about how best to emulate partial classes.

Here are a few thoughts:

  • The first thing that springs to mind is inheritance. It's not necessarily the best solution always, but you may not have a choice since you may need to be able to have your objects be able to be treated like the base class.
  • Composition is also a good choice (that is, wrapping the class in another class). This gives you a little bit nicer decoupling from your DAL, but can be tedious to implement.
  • If you really just need to add a method or two onto an existing class, you might also consider using an extension method, but these can quickly create spaghetti code if you use them too much.
Jason Baker
  • 192,085
  • 135
  • 376
  • 510
4

Partial classes have to exist in the same assembly. Otherwise, how would the compiler decide where to merge the partial classes to?

Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • Actually, I thought that once I put them in the same namespace they would compile to the same assembly (in essence creating a unique assembly for each project) – Armstrongest Nov 21 '08 at 20:46
1

I agree with Jon Skeet's answer.

I don't think it would be a good choice to approach an issue like this anyway. There are good design patterns out there already that demonstrate the best way to split your tiers/layers of code, and this is just a little syntactic sugar so that Microsoft could make the WinForms/WebForms designer files separate and prevent people from breaking them.

Neil Barnwell
  • 41,080
  • 29
  • 148
  • 220
  • 3
    Got any suggestions as a good design pattern to follow? My DAL is built using LINQ, so that's why I have less control over the Actual Data Classes. – Armstrongest Nov 21 '08 at 20:47
1

I see no reason why this scheme would not work:

Two files contain storage mechanisms (or some other feature). They specify inheritance but contain no business logic:

  • ProductDataAccess.cs
  • ProductWeb.cs

One file contains the business logic:

  • ProductBusinessLogic.cs

Now create two projects:

  • WebProject contains ProductWeb.cs and ProductBusinessLogic.cs.
  • DataProject contains ProductDataAccess.cs and ProductBusinessLogic.cs

Both projects make use of the same business logic.

0

No.You cant write partial classes in different projects.Because at a time compiler gets a single project for compilation and so scans for list of classes,methods,fields etc in that project only.So if you have some parts of the partial class in other projects,compiler cant find those.

user3363647
  • 105
  • 2
  • 5
0

While I agree with you Neil when it comes to pre-linq development, I also wish I could be able to do this in order to split up bussiness logic from partial classes generated by Linq2SQL designer. For example:

Northind.DAL (prj)
-NorthindDataContext (EntityNamespace set to "Northwind.BLL")
--Product() (Entity, partial class auto-generated)
--Category() (Entity, partial class auto-generated)
--Supplier() (Entity, partial class auto-generated)

Northind.BLL (prj)
-Product() : IMyCustomEnityInterface, BaseEntity (override OnValidate(), etc)
-Category() : IMyCustomEnityInterface, BaseEntity (override OnValidate(), etc)
-Supplier() : IMyCustomEnityInterface, BaseEntity (override OnValidate(), etc)

Unfortunately, we can not do this... actually I'd love to know what the recommended way of splitting up layers/tiers when using LINQ.

user47460
  • 31
  • 1
  • 1
  • 2