Friend Assemblies: accessing internal classes externally
What is it?
Normally, only classes contained within an assembly (a .NET dll) can access other types and members contained within that assembly, when those types and members are marked as internal. Friend assemblies is a handy way of allowing classes outside of the assembly those same access privileges. In other words, a class within assembly B can access internal classes and methods etc within assembly A if the latter regards B as a friend assembly.
Which C# version supports it?
Version 2.0 and above.
Why use it?
The single biggest, most compelling, reason for using friend assemblies is when doing unit testing. Without friend assemblies, unit testing solutions are less than appealing, such as:
- Don’t do unit testing
- Only test public classes and methods
- Make everything public
- Build your unit tests into the production-code assembly
With friend assemblies though, the unit test code can be kept in a separate assembly and given full access the the internals of the code it wishes to test. At the same time, those internals are kept hidden from all other assemblies.
There are other uses, such as for splitting overly-large, unwieldy projects across multiple assemblies. These tend to be the result of poor design though and are better fixed by refactoring the code. I won’t cover these uses here therefore.
How to use it
If you are using Visual Studio, friend assemblies are easy to use. If not, you have to be aware of some extra compiler options, which I’ll cover at the end. The key to friend assemblies is the InternalsVisibleToAttribute attribute. It is a self descriptive attribute: it denotes that the internals of an assembly will be made visible to the specified second assembly. It takes the general form:
[assembly:InternalsVisibleToAttribute("assembly name",
"optional key string")]
The optional key string is used when the assemblies are signed and it’s use is explained below.
Unsigned Friend Assemblies
These are the easiest to use. Consider the following code:
AssemblyA
ClassInternal.cs
using System;
internal class ClassInternal
{
internal void Method1()
{
Console.WriteLine("Method1");
}
private void Method2()
{
Console.WriteLine("Method2");
}
}
ClassPublic.cs
using System;
public class ClassPublic
{
public void Method3()
{
Console.WriteLine("Method3");
}
internal void Method4()
{
Console.WriteLine("Method4");
}
private void Method5()
{
Console.WriteLine("Method5");
}
}
AssemblyB
FriendlyClass.cs
using System;
public class FriendlyClass
{
public void Test()
{
ClassPublic pub = new ClassPublic();
ClassInternal intern = new ClassInternal();
intern.Method1();
intern.Method2();
pub.Method3();
pub.Method4();
pub.Method5();
}
}
Compile the above code and you’ll get the following errors:
...\AssemblyB\FriendlyClass.cs(8,9): error CS0122:
'ClassInternal' is inaccessible due to its protection level
...\AssemblyB\FriendlyClass.cs(8,36): error CS0122:
'ClassInternal' is inaccessible due to its protection level
...\AssemblyB\FriendlyClass.cs(8,32): error CS0143:
The type 'ClassInternal' has no constructors defined
...\AssemblyB\FriendlyClass.cs(10,16): error CS0117:
'ClassInternal' does not contain a definition for 'Method1'
...\AssemblyB\FriendlyClass.cs(11,16): error CS0117:
'ClassInternal' does not contain a definition for 'Method2'
...\AssemblyB\FriendlyClass.cs(14,13): error CS0117:
'ClassPublic' does not contain a definition for 'Method4'
...\AssemblyB\FriendlyClass.cs(15,13): error CS0117:
'ClassPublic' does not contain a definition for 'Method5'
Change one of the ClassInternal.cs or ClassPublic.cs (it makes no difference which) to regard Assembly B as friendly though and the number of errors changes. So by changing ClassInternal.cs to be:
using System;
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleToAttribute("AssemblyB")]
internal class ClassInternal
{
internal void Method1()
{
Console.WriteLine("Method1");
}
private void Method2()
{
Console.WriteLine("Method2");
}
}
we now only get the following errors:
...\AssemblyB\FriendlyClass.cs(11,16): error CS0117:
'ClassInternal' does not contain a definition for 'Method2'
...\AssemblyB\FriendlyClass.cs(15,13): error CS0117:
'ClassPublic' does not contain a definition for 'Method5'
In case you are wondering why we still have errors, it is because Method2 and Method5 are declared private. The InternalsVisibleToAttribute attribute only exposes internal types and members. Private members remain private.
Signed Friend Assemblies
Signed assemblies are more tricky to use with the friend assembly mechanism. The reason is simply sue to the unwieldy nature of keys. In our above example, if AssemblyA were signed, then AssemblyB must be signed too. The result of signing AssemblyA in the first instance is the error:
error CS1726: Friend assembly reference 'AssemblyB' is invalid.
Strong-name signed assemblies must specify a public key
in their InternalsVisibleTo declarations.
What that means is that AssemblyB must be signed too and you then must alter the attribute to something like:
[assembly:InternalsVisibleTo("AssemblyB, PublicKey=002400000480000094000000 0602000000240000525341310004000001 000100031d7b6f3abc16c7de526fd67ec2 926fe68ed2f9901afbc5f1b6b428bf6cd9 086021a0b38b76bc340dc6ab27b65e4a59 3fa0e60689ac98dd71a12248ca025751d1 35df7b98c5f9d09172f7b62dabdd302b2a 1ae688731ff3fc7a6ab9e8cf39fb73c606 67e1b071ef7da5838dc009ae0119a9cbff 2c581fc0f2d966b7114b2c4")] internal class ClassInternal { ...
Getting that key is a fiddly task. However I have created a handy little application (complete with source code) that does the job for you.
To finish, I mentioned earlier that you must use extra compiler flags if you are not using Visual Studio. Check out the MSDN article on friend assemblies for the full details.
No commentsNo comments yet. Be the first.
Leave a reply
