Reference Aliasing
In .NET 2.0, by default, all types are rooted in a namespace called global. For example, this class definition...
class MyClass
{}
...is identical to this one:
namespace global
{
class MyClass
{}
}
You don't need to explicitly use the global namespace since it is always implied by default. The global namespace is instrumental in resolving type name conflicts, especially when multiple assemblies are involved.
When you add an assembly reference, it is possible to create a conflict with another type already defined by your application in another assembly it references. For example, consider the
MyApplication.exe and
MyLibrary.dll assemblies, which both define the class MyClass in the namespace MyNamespace.
//In MyApplication.exe
namespace MyNamespace
{
public class MyClass
{...}
}
//In MyLibrary.dll
namespace MyNamespace
{
public class MyClass
{...}
}
Each definition of MyClass is completely distinct, providing different methods and behaviors. If you add a reference to MyLibrary.dll from within MyApplication.exe, when you try to use the type MyClass like so:.
using MyNamespace;
MyClass obj = new MyClass();
The compiler will issue an error because it does not know how to resolve itthat is, it doesn't know which definition of MyClass to reference.
C# 2.0 allows you to resolve type name conflicts by aliasing the assembly reference. By default, all namespaces are rooted in the global namespace. When aliasing an assembly, the namespaces used in that assembly will be resolved under the alias, not globally. To alias an assembly, first add a reference to it in Visual Studio 2005. Next, expand the Reference folder in the Solution Explorer, and display the properties of the referenced assembly (see
Figure 2).
 | |
| Figure 2: Aliasing an assembly reference. |
If you added the reference by browsing to the assembly, the
Aliases property will be set explicitly to
global. If you added the reference by selecting the assembly from the Projects tab, the
Aliases value will be empty (but implicitly global). You can specify multiple aliases, but for addressing most conflicts a single alias will do (unless you also have conflicts with other aliases).
Next, add as the first line of the file the
extern alias directive, instructing the compiler to include the types from the alias in the search path.
extern alias MyLibraryAlias;
You can now refer to the class MyClass from MyLibrary.dll.
MyLibraryAlias::MyNamespace.MyClass obj;
obj = new MyLibraryAlias::MyNamespace.MyClass();
Note that the
extern alias directive must appear before any
using directives, and that all types in the
MyLibrary.dll can only be referred to via the alias, because these types are not imported to the global scope.
Using aliases and fully qualified namespaces may result in exceedingly long lines. As shorthand, you can also alias the fully qualified name.
C# 2.0 allows you to resolve type name conflicts by aliasing the assembly reference.
|
|
using MyLibrary = MyLibraryAlias::MyNamespace;
MyLibrary.MyClass obj;
obj = new MyLibrary.MyClass();
Friend Assemblies
An interesting assembly-level attribute introduced by .NET 2.0 is the
InternalsVisibleTo attribute, defined in the System.Runtime.CompilerServices namespace. The
InternalsVisibleTo attribute allows you to expose internal types and methods to clients from another specified assembly. This is also known as declaring a friend assembly. For example, suppose the server assembly
MyClassLibrary.dll defines the internal class MyInternalClass as.
internal class MyInternalClass
{
public void MyPublicMethod()
{...}
internal void MyInternalMethod()
{...}
}
Suppose you add the following line to the
AssemblyInfo.cs file of MyClassLibrary.dll.
[assembly: InternalsVisibleTo("MyClient")]
Now any client in the assembly MyClient.dll and MyClient.exe can use MyInternalClass and call its public or internal members. In addition, any sub class in the MyClient assembly can access members marked as protected internal. Declaring an assembly as a friend could easily be abused and violate the essential encapsulation of the internals of the assembly, and tightly couple the client to the internals of the server assembly. Declaring a friend assembly is available for when you break an existing assembly into one or more assemblies by moving some of the types to new assemblies. If the relocated types still rely on internal types in the original assembly, declaring a friend assembly is a quick (albeit potentially dirty) way of enabling the move. Another case where a friend assembly is handy is when you want to test internal components but the text client resides in different assembly.
The InternalsVisibleTo attribute allows you to expose internal types and methods to clients from another specified assembly.
|
|
When you apply the
InternalsVisibleTo attribute like so:
[assembly: InternalsVisibleTo("MyClient")]
It does not matter whether the MyClient assembly has a friendly name or a strong name in both cases it is allowed access to the internals of the server. Obviously this is not a very secure or safe way of exposing your internal types. Using the
InternalsVisibleTo attribute should be restricted to assemblies developed in conjunction with the server assembly. All a third-party assembly has to do to access the server assembly internals is to change its friendly name. In addition, this use of the
InternalsVisibleTo attribute is version indifferentany version of the MyClient assembly can access the server internals.
To provide the additional degree of security to the
InternalsVisibleTo attribute, the server assembly can also specify the token of the public key of the client assembly.
[assembly:InternalsVisibleTo(
"MyClient,PublicKeyToken=745901a54f88909b")]
When specifying the public key token, the server assembly grants permission to access its internals only for client assemblies with a matching friendly and strong name, and the client-side compiler enforces that.
In addition to the friendly name of the client assembly, the server assembly can specify a version number.
[assembly:InternalsVisibleTo(
"MyClient,Version=1.0.0.0")]
The effect of the version number depends on whether or not the client assembly has a strong name.
If the client assembly has only a friendly name, then the version stipulation has no affect, and such clients can freely access the internals of the server assembly. If, however, the client does have a strong name (any strong name), then the client-side compiler will insist that only a client assembly with a matching strong name and version number can access the internals. Note that this behavior is akin to reverse-versioning, where the server constrains the version of its clients.
Of course, the server assembly can specify both a version number and a public key token.
[assembly:InternalsVisibleTo(
"MyClient,Version=1.0.0.0,
PublicKeyToken=745901a54f88909b")]
In which case, only clients with a matching strong name and version number can access the internals.