C# Friendly Assemblies: Looking at Building Truly Reusable Components

Overview

We will accomplish building reusability into our project by exposing the interfaces to our objects publicly while hiding the implementation in an assembly containing only internal classes and then exposing the interfaces to our classes in a "builder" assembly.  The builder assembly will be the only assembly allowed to make instances of the core objects so the instantiation of our objects are effectively hidden and not available outside the builder classes.

Project Core

For this project we will build a simple Money object that can be added. Our four assemblies will consist of

  1. FriendlyTesting.Core: Contains our interface definitions.
  2. FriendlyTesting.Hidden: Contains our implementation in classes internal to this assembly.
  3. FriendlyTesting.Friend: Exposes instantiation of the internal classes in FriendlyTesting.Hidden.
  4. FriendlyTesting.TestHarness: The final test harness console project.

assembly1.gif

Interfaces

Our interfaces define an IMoney interface that is IAddable<IMoney> (so we can add up our money in the test harness).

assembly2.gif

Concrete Implementation

The internal Money class implements IMoney and is "hidden" because it can only be instantiated from inside the containing assembly or a friend assembly.

assembly3.gif

Builders

The builder class will implement an interface specific to the builder: IMoneyMaker. The builder's responsibility is to instantiate the Money object and expose it through the IMoney interface. 

assembly4.gif

Our solution

The first thing to note is that the Money is hidden in it's assembly because it is marked as internal.

internal class Money: IMoney, IAddable<IMoney>

As a result, from our money builder class, the money is not available because the Money class is only internally available to the FriendlyTesting.Hidden assembly.  As a result, we can not instantiate a Money object until we make the FriendlyTesting.Friend assembly a "friend" of the FriendlyTesting.Hidden assembly indicating that it can be trusted with the internal classes.

public class MoneyBuilder: IMoneyMaker

{

 

    #region IMoneyMaker Members

 

    public IMoney MakeMoney(double amount)

    {

        // Money is not available here...

        // it is internal to FriendlyTesting.Hidden

    }

 

    #endregion

}

Creating a Friendly Assembly

Our goal is to make instantiation possible only through our builder class, so in the next few steps we'll make these two assemblies play nicely with each other.

Step 1) Strongly Naming the Assemblies

The first step is to make all the assemblies strong named.  So we will create keys for our two assemblies (friend and hidden) through the "sn"  visual studio command line utility (Start>Programs>Visual Studio>Visual Studio Tools>Visual Studio Command Prompt).

Command line syntax:

sn -k <new key name>

Actual command:

sn -k FriendlyKey.snk

Generating the key for the FriendlyTesting.Friend assembly:

assembly5.gif
 
Generating the key for the FriendlyTesting.Hidden assembly:

assembly6.gif

Because these two assemblies are strongly named, all referenced assemblies also have to be strongly named, so we'll do the same thing for the FriendlyTesing.Core assembly that holds our core interface definitions.

Step 2) Add the Keys to the Projects

Now we have to add the keys we generated to their respective projects through the project's properties (right click on the project and select 'Properties').  We'll walk through this for the FriendlyTesting.Friend assembly:

assembly7.gif
 
Click "Sign the assembly" and select browse from the drop down that is activated.

Open FriendKey.snk and it will now appear in our project

assembly8.gif

Add HiddenKey.snk to the friendlyTesting.Hidden project and the core key to the core project in the same way.

assembly9.gif

Step 3) Extract public key for friend assembly

Next we will extract the public key from our FriendlyKey.snk (the key for the FriendlyTesting.Friend project).  We need information from the public key in order to let the hidden project know which assemblies to be friendly with.  We will generate a new public key called "FriendlyKey_public.snk" with the following command:

Command line syntax:

sn -p <existing private key name> <new public key to be created>

Actual command:

sn -p FriendlyKey.snk FriendlyKey_public.snk

assembly10.gif
 
Step 4) find out the public key's info

Next we will show the public key's info with the following command

Command line syntax:

sn -tp <public key name>

Actual command:

sn -tp FriendlyKey_public.snk

assembly11.gif

Step 5) Name a friend in the hidden assembly.

Next, we will update the Assembly.cs file in the FriendlyTesting.Hidden project making our hidden assembly's internal member available to the new friend.

Add the following line which contains the public key information we just gathered to the FriendlyTesting.Hidden Assembly.cs file.  Assembly.cs is located in the project's Properties folder:

[assembly: InternalsVisibleTo("FriendlyTesting.Friend, PublicKey=00240000048000009400000006020000002400005253413100040000010
001000bfc58cfde00927bad3d28eb63098979418a31af879120f08c8babb49c998a0a2a
f6416679763add28c735f3e8503301336339c321cfd23b6a346df22b32bf83e01c2aac16
f5e64c355c1c66ecc892c6e8986a2c1fc05fcc5f90f595decf968506b41c64d49cfe5431d
eeed3d179c09c871eac6b10fcad24f473bcd3731a1fb2"
)]

After this we find that FriendlyMoney.Friend has access to the internal members of the FriendlyTesting.Hidden project.  So we can make money with our MoneyBuilder.

public class MoneyBuilder: IMoneyMaker

{

    #region IMoneyMaker Members

 

    public IMoney MakeMoney(double amount)

    {

        return FriendlyTesting.Hidden.Money.Instantiate(amount);

    }

 

    #endregion

}

Wrap up: Test Harness

In any other projects in which we would like to use our Money class, we can only instantiate it through the MoneyBuilder and our Money object can now only be referenced through the IMoney or IAddable<> interfaces. 

class Program

{

    static void Main(string[] args)

    {

        IMoneyMaker builder = MoneyBuilder.Instantiate();

 

        // NOTE: FriendlyTesting.Hidden has no members available here

        // so we are required to get IMoney through the MoneyBuilder

       

        IMoney bagODoughA = builder.MakeMoney(10.25);

        IMoney bagODoughB = builder.MakeMoney(5.50);

        IMoney allDough = bagODoughA.Add(bagODoughB);

 

        Console.WriteLine("Total Moolah: " +

             allDough.Amount.ToString());

 

        Console.ReadLine();

    }

}

 

This approach gives us a great deal of flexibility because we control how our class will be consumed by other projects and we can ensure nothing will break by changing backend implementation... as long as the code works and the IMoney and IAddable<> interfaces do not change.

 

If you download the project, you can verify that the Money object is not available to the FriendlyTesting.TestHarness assembly. Also, if you remove the line added to FriendlyTesting.Hidden Assembly.cs, the project will not compile because the Money object will no longer be visible to the FriendlyTesting.Friendly assembly.

I hope you found this article useful.

Until next time,
Happy coding 


Similar Articles