C# provides a lot of scoping options for classes, methods, and properties. However, one scoping option that you may think is missing is the ability to allow a DLL to reference an internally scoped method that resides in another DLL.
Why would I want to do this?
Q. Why would you want to reference an internal method in another DLL? Didn’t the author of the DLL scope it internal specifically to prevent such access?
A. The answer is “Yes”, but sometimes, the author of LOWLEVEL.DLL and the author of HIGHLEVEL.DLL are the same person or on the same team and they trust each other.
Q. So why not just scope the method as public in LOWLEVEL.DLL?
A. The reason is that you don’t want 3rd party programmers to access the method directly; but you do want your own DLLS to be able to access them.
When would I want to do this?
There are 2 scenarios where this is common:
One scenario is that you have a method in LOWLEVEL.DLL that runs an SQL query in the database. The method is generic and could be used by several HIGHLEVEL.DLLs, but you don’t want to make the method public because you don’t want 3rd party programmers to run SQL, you want 3rd party programmers to go through your HIGHLEVEL.DLLs that check permissions and syntax before calling the base LOWLEVEL.DLL to run the query.
Another scenario where you want to call internal methods in other DLLs is when you want to write unit tests to test those internal methods, and you don’t want to include your unit test assembly in the same solution as the DLL you are testing.
How do I do this?
To call an internal method in another DLL you must:
-
Strong name both DLLs
-
Update Assembly.info of the LOWLEVEL.DLL to allow the specific HIGHLEVEL.DLL access to internal methods
Strong Naming DLLs
You can think of strong-naming as a way to make your DLL globally unique. You want to use a strong name when allowing access to internal methods; otherwise, 3rd party programmers could simply name their DLL to the simple name that your LOWLEVEL.DLL allows.
A number of articles provide information for strong naming DLLs, such as this one: http://msdn.microsoft.com/msdnmag/issues/06/07/CLRInsideOut/default.aspx, or this one: http://msdn2.microsoft.com/en-us/library/xwb8f617(VS.80).aspx
Identifying the DLLs to allow access
You must add an entry in the AssemblyInfo.cs file of the LOWLEVEL.DLL (the DLL that has the internal method to be accessed by another DLL) in order to identify the HIGHLEVEL.DLL that is allowed to access internal methods. You do this with the InternalsVisibleToAttribute attribute (aka InternalsVisibleTo). Unfortunately, as I researched this I found many examples that were not exactly accurate.
This example will work:
[assembly: InternalsVisibleToAttribute(@”HighLevel, PublicKey=0024000004800000940000000602000000240000…BigLongValue…”)]
-
You must include the CompilerServices namespace in AssemblyInfo.cs:
-
You must use the long form of your public key name. You can obtain the long name of the public key by running the sn tool at the command prompt:
-
You must use InternalsVisibleToAttribute (not InternalsVisibleTo)
Compile both DLLs with their strong names. Then, recompile the LOWLEVEL.DLL with the above attribute, and the HIGHLEVEL.DLL should be able to access internal methods of the LOWLEVEL.DLL.
One step further:
For those of you using NANT to automatically create your AssemblyInfo.cs files, you need to do three things:
-
Add the CompilerServices namespace,
-
Add the InternalsVisibleToAttribute attribute as shown below
-
Include the keyfile on the csc task when building the dll.
<asminfo output=”Properties/AssemblyInfo.cs” language=”CSharp”>
<imports>
<import namespace=”System” />
<import namespace=”System.Reflection” />
<import namespace=”System.Runtime.InteropServices” />
<import namespace=”System.Runtime.CompilerServices” />
</imports>
<attributes>
<attribute type=”ComVisibleAttribute” value=”false” />
<attribute type=”CLSCompliantAttribute” value=”true” />
<attribute type=”InternalsVisibleToAttribute” value=”HighLevel, PublicKey=002400000480000094000…BigLongValue…” />
</attributes>
</asminfo>
<csc target=”library” platform=”x86″ warnaserror=”true” output=”${build.dir}/bin/${project.name}.dll” keyfile=”mykey.snk“>