Tuesday, July 22, 2003 5:34 PM Shawn A. Van Ness

VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

I'm surprised by how many people don't know this trick...  but equally surprised by how obscure the UI is.

The Problem

Let's say you have a clever little bit of C# code you'd like to reuse, but it's something too trivial to warrant its building, supporting, and maintaining its own assembly.  Or perhaps the code is something proptrietary, and you'd like to obfuscate it as fully as possible -- which usually means not exposing it in any public types.  Or perhaps it's just a small set of compile-time constants that you'd like to bring into multiple assemblies.

As a trivially simple example, perhaps you get tired of specifying the same AssemblyCompany and AssemblyCopyright strings, over and over again, in all your projects?  Wouldn't it be nice to have a little two-line C# file tucked away somewhere, and include it by reference in all your projects?

[assembly: System.Reflection.AssemblyCompany("Arithex.com")]
[assembly: System.Reflection.AssemblyCopyright("© 2003 Shawn A. Van Ness. All rights reserved.")]

With C++ this is trivial -- you create a header file, and #include it from wherever you like.  But C# doesn't have a #include directive.  Or does it?

The Solution

No, it doesn't (sorry to tease ;-).  But Visual Studio .NET does offer a way to bring in C# code from a source file that's nowhere near the rest of the source files in your project directory -- it's not intra-file lexicographic inclusion, as in C++, but every C# source file can see every other C# source file in the build, so in practice there's not much difference!

Unfortunately, the UI to accomplish this task is pretty obscure -- which is probably why I run into so few developers who know about it.  It's so obscure, in fact, that I often find it easier to close VS and hack the .csproj file manually.  Here's what a <File> element might look like, for a C# project file (a .csproj file) that references a shared definition for my hypothetical AssemblyCompany and AssemblyCopyright strings, above:

   <File
      RelPath = "AssemblyCompanyAndCopyright.cs"
      Link = "..\..\AssemblyCompanyAndCopyright.cs"
      SubType = "Code"
      BuildAction = "Compile"
   />

Note that "RelPath" attribute is not, as you might think, a relative path to the file.  That would make sense!  Rather, it's the relative path of where the node will appear in the Solution Explorer treeview.  (This is the same "RelPath" attribute that you see on every other file in your C# project.)  The "Link" attribute is the interesting one -- that's the relative path to the actual file (relative to the .csproj file).  In Solution Explorer, the "linked' file will appear with an Explorer-style shortcut arrow emblazoned over its icon.

If you have some trepidation about hacking up your .csproj files, or if you absolutely, positively insist on using a mouse...  right-click your project in Solutions Explorer, and select Add Existing Item.  Browse to your shared C# source file, then click the unnoticeable little dropdown arrow next to the Open button.  See the ctxmenu item entitled "Link File"?  That's your man.

The Catch

(There's always a catch.)  For anything more complex than assembly-level attributes, one must think carefully about duplicating C# code across multiple assemblies.  Consider the following hypothetical SharedConstants.cs file...

using System;
namespace MyCompany {
internal class SharedConstants {

    public const string Hello = "Hello";
    public const string World = "World";
    }
}

This will produce a new, different type named MyCompany.SharedConstants in each and every assembly you "include" it in.  If the constants are large, or if the sanctity of SharedConstants' type-identity is important to you (imagine, perhaps, defining an enum this way, then later developing methods and properties which attempt to pass parameters of that type across assembly boundaries) then this approach may be undesirable -- or it may flat out break.

In that case, your only real option is to build a shared assembly which defines the constants (or enumeration values, etc), then ref that assembly in your other projects.  And that is, of course, what the founding fathers of .NET intended.

Comments

# {Sudhakar's .NET Dump Yard;}

Tuesday, July 22, 2003 8:34 PM by TrackBack

{Sudhakar's .NET Dump Yard;}

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Wednesday, July 23, 2003 2:08 AM by Jerry Dennany

This rocks - thanks!

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Wednesday, July 23, 2003 6:47 AM by RichB

The second catch is that this is only available in VS.Net 2003.

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Wednesday, July 23, 2003 7:16 AM by Shawn A. Van Ness

I've been "linking" to C# files, successfully, since v7.0. What problem are you having?

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Wednesday, July 23, 2003 7:30 AM by Ian Griffiths

Works fine for me in VS.NET 2002 as well.

One problem that I have encountered with this is in a similar scenario to your example: sharing assembly level attributes. I wanted all my component to have the same strong name and version number too. Works fine, right up until you have some depth to your filesystem hierarchy... At which point, you either need multiple copies of your key files, or you need to move to the alternative crypto container approach.

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Wednesday, July 23, 2003 2:21 PM by Ethan J. Brown

FYI -- this is a no go for ASP.NET projects...

Works fine otherwise...

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Saturday, February 14, 2004 10:50 PM by Lee Mighdoll

Thanks for posting this tip. I was just about to do something awkward instead.

# Any way to make this work in ASP.NET

Monday, February 16, 2004 12:34 PM by William Walseth

Thanks for posting this.

The solution works really well in console and stand-alone applications, not in ASP.NET. The 'link' option doesn't appear in the 'Open' dialog. Manually editing the .csproj file fails too, it doesn't seem to link in the file, or display it in the Solution Explorer. UGH!

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Wednesday, February 18, 2004 3:15 AM by Shawn A. Van Ness

As Ethan says above, this is a no-go for ASP.NET projects.

That doesn't bother me, because there are about 97 other things about VS's handling of ASP.NET projects that I find utterly unacceptable.

I recommend folks build all significant ASP.NET code as conventional "Class Library" projects (aka DLLs ;) and write only the most superficial UI code in the pages, to call into those libraries.

VS hokeyness aside, you'll be extra glad when you get around to developing alternative site UIs for different platforms and devices (think: mobile).

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Friday, March 05, 2004 10:58 AM by Greg Robinson

I assume this is a C# thing only as I do not see this in the IDE when using VB.NET.

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Friday, March 05, 2004 11:35 AM by Shawn A. Van Ness

No, it's there, Greg. Look harder.

(Note the comments above -- it's not there, for ASP.NET projects.)

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Tuesday, March 23, 2004 12:46 AM by Joe Ellsworth

Thanks for your recommendation. It provides the best way I have found yet to work around a substantial design flaw in the way .NET as a whole treats cross-architecture (OS) systems. Microsoft needs to come up with a better solution.

We have an extensive smart device project that should run easily on the desktop with less than 3% code change. As it turns out the Visual Studio environment makes this almost impossible without creating two complete source trees. Your recommended solution will help but will be expensive to sustain over time since I will have to maintain a separate project for each primary output architecture and then add a link to new files to every project every time.

I am a C# advocate because at the language level it fixes many things that have frustrated me for years with Java. I find it frustrating that Microsoft made this mistake, which could have been avoided with a little forethought.

In Java and Python I can simply copy my compiled class or JAR file anywhere the JVM exists and it will run without change, recompilation or re-linking. If I have that inevitable 3% to 5% of source that changes across operating systems, I simply change the Class Path to include directories where the modules specific for my local OS are located and it works like magic.

For Microsoft to ignore large scale cross OS execution and build strategies like this are almost unforgivable and clearly indicate that they did not think their deployment strategy for large scale cross OS environments through.

Microsoft should seriously consider adopting the class path or Python path environment variable strategy. They work much better to give implementation architects better choices without having to resort back to the development environment. EG: Replacing one XML implementation with a different one that is API compatible. As a whole C# would substantially benefit from adopting a more portable output like Python or Java.

The solution above sidesteps the issue of why the Smart device application does not automatically build a version that will run on XP. It is already a sub set and should be able to run through the standard .NET libraries unless the device specific features are used. In Java, we would not even need a separate version; it would work simply by copying the class file, changing the environment variable to point at the right supporting classes and running. It should be this easy in .NET except that Microsoft made a fundamental design mistake in their compilation and link strategy for .NET.

# re: VS.NET Tip of the Day: Reusing C# Source Code Across Multiple Assemblies

Sunday, July 04, 2004 7:17 AM by Aryana

Thanks for your valuable tip. I defined a SharedConstants class as you described in the catch but I don't know How can I include it in my source code. Can you help me please?