RAD Studio (Common)
ContentsIndex
PreviousUpNext
Using Namespaces with Delphi

In Delphi, a unit is the basic container for types. Microsoft's Common Language Runtime (CLR) introduces another layer of organization called a namespace. In the .NET Framework, a namespace is a conceptual container of types. In Delphi, a namespace is a container of Delphi units. The addition of namespaces gives Delphi the ability to access and extend classes in the .NET Framework.  

Unlike traditional Delphi units, namespaces can be nested to form a containment hierarchy. Nested namespaces provide a way to organize identifiers and types, and are used to disambiguate types with the same name. Since they are a container for Delphi units, namespaces may also be used to differentiate between units of the same name, that reside in different packages. 

For example, the class MyClass in MyNameSpace, is different from the class MyClass in YourNamespace. At runtime, the CLR always refers to classes and types by their fully qualified names: the assembly name, followed by the namespace that contains the type. The CLR itself has no concept or implementation of the namespace hierarchy; it is purely a notational convenience of the programming language.  

The following topics are covered:

  • Project default namespaces, and namespace declaration.
  • Namespace search scope.
  • Using namespaces in Delphi units.

In RAD Studio, a project file (program, library, or package) implicitly introduces its own namespace, called the project default namespace. A unit may be a member of the project default namespace, or it may explicitly declare itself to be a member of a different namespace. In either case, a unit declares its namespace membership in its unit header. For example, consider the following explicit namespace declaration:

unit MyCompany.MyWidgets.MyUnit;

First, notice that namespaces are separated by dots. Namespaces do not introduce new symbols for the identifiers between the dots; the dots are part of the unit name. The source file name for this example is MyCompany.MyWidgets.MyUnit.pas, and the compiled output file name is MyCompany.MyWidgets.MyUnit.dcuil.  

Second, notice that the dots imply the conceptual nesting, or containment, of one namespace within another. The example above declares the unit MyUnit to be a member of the MyWidgets namespace, which itself is contained in the MyCompany namespace. Again, it should be noted that this containment is for documentation purposes only.  

A project default namespace declares a namespace for all of the units in the project. Consider the following declarations:

Program MyCompany.Programs.MyProgram;
Library MyCompany.Libs.MyLibrary;
Package MyCompany.Packages.MyPackage;

These statements establish the project default namespace for the program, library, and package, respectively. The namespace is determined by removing the rightmost identifier (and dot) from the declaration.  

A unit that omits an explicit namespace is called a generic unit. A generic unit automatically becomes a member of the project default namespace. Given the preceding program declaration, the following unit declaration would cause the compiler to treat MyUnit as a member of the MyCompany.Programs namespace.

unit MyUnit;

The project default namespace does not affect the name of the Delphi source file for a generic unit. In the preceding example, the Delphi source file name would be MyUnit.pas. The compiler does however prefix the dcuil file name with the project default namespace. The resulting dcuil file in the current example would be MyCompany.Programs.MyUnit.dcuil.  

Namespace strings are not case-sensitive. The compiler considers two namespaces that differ only in case to be equivalent. However, the compiler does preserve the case of a namespace, and will use the preserved casing in output file names, error messages, and RTTI unit identifiers. RTTI for class and type names will include the full namespace specification.

A unit must declare the other units on which it depends. As with the Win32 platform, the compiler must search these units for identifiers. For units in explicit namespaces the search scope is already known, but for generic units, the compiler must establish a namespace search scope.  

Consider the following unit and uses declarations:

unit MyCompany.ProjectX.ProgramY.MyUnit1;
uses MyCompany.Libs.Unit2, Unit3, Unit4;

These declarations establish MyUnit1 as a member of the MyCompany.ProjectX.ProgramY namespace. MyUnit1 depends on three other units: MyCompany.Libs.Unit2, and the generic units, Unit3, and Unit4. The compiler can resolve identifier names in Unit2, since the uses clause specified the fully qualified unit name. To resolve identifier names in Unit3 and Unit4, the compiler must establish a namespace search order.

Namespace search order

Search locations can come from three possible sources: compiler options, the project default namespace, and the current unit's namespace.  

The compiler resolves identifier names in the following order:

  1. The current unit namespace (if any)
  2. The project default namespace (if any)
  3. Namespaces specified by compiler options.
 

 

A namespace search example

The following example project and unit files demonstrate the namespace search order:

// Project file declarations...
program MyCompany.ProjectX.ProgramY;

 

// Unit source file declaration...
unit MyCompany.ProjectX.ProgramY.MyUnit1;

Given this program example, the compiler would search namespaces in the following order:

  1. MyCompany.ProjectX.ProgramY
  2. MyCompany.ProjectX
  3. Namespaces specified by compiler options.
Note that if the current unit is generic (i.e. it does not have an explicit namespace declaration in its unit statement), then resolution begins with the project default namespace.

Delphi's uses clause brings a module into the context of the current unit. The uses clause must either refer to a module by its fully qualified name (i.e. including the full namespace specification), or by its generic name, thereby relying on the namespace resolution mechanisms to locate the unit.

Fully qualified unit names

The following example demonstrates the uses clause with namespaces:

unit MyCompany.Libs.MyUnit1
uses MyCompany.Libs.Unit2,  // Fully qualified name.
     UnitX;                 // Generic name.

Once a module has been brought into context, source code can refer to identifiers within that module either by the unqualified name, or by the fully qualified name (if necessary, to disambiguate identifiers with the same name in different units). The following writeln statements are equivalent:

uses MyCompany.Libs.Unit2;

begin
   writeln(MyCompany.Libs.Unit2.SomeString);
   writeln(SomeString);
end.

A fully qualified identifier must include the full namespace specification. In the preceding example, it would be an error to refer to SomeString using only a portion of the namespace:

writeln(Unit2.SomeString);       // ERROR!
writeln(Libs.Unit2.SomeString);  // ERROR!
writeln(MyCompany.Libs.Unit2.SomeString);      // Correct.
writeln(SomeString);                           // Correct.

It is also an error to refer to only a portion of a namespace in the uses clause. There is no mechanism to import all units and symbols in a namespace. The following code does not import all units and symbols in the MyCompany namespace:

uses MyCompany;   // ERROR!

This restriction also applies to the with-do statement. The following will produce a compiler error:

with MyCompany.Libs do    // ERROR!

 

Namespaces and .NET Metadata

The Delphi for .NET compiler does not emit the entire dotted unit name into the assembly. Instead, the only leftmost portion - everything up to the last dot in the name is emitted. For example:

unit MyCompany.MyClasses.MyUnit

The compiler will emit the namespace MyCompany.MyClasses into the assembly metadata. This makes it easier for other .NET languages to call into Delphi assemblies.  

This difference in namespace metadata is visible only to external consumers of the assembly. The Delphi code within the assembly still treats the entire dotted name as the fully qualified name.

Multi-unit Namespaces

Multiple units can belong to the same namespace, if the unit declarations refer to the same namespace. For example, one can create two files, unit1.pas and unit2.pas, with the following unit declarations:

// in file 'unit1.pas'
unit MyCompany.ProjectX.ProgramY.Unit1

 

// in file 'unit2.pas'
unit MyCompany.ProjectX.ProgramY.Unit2

In this example, the namespace MyCompany.ProjectX.ProgramY logically contains all of the interface symbols from unit1.pas and unit2.pas

Symbol names in a namespace must be unique, across all units in the namespace. In the example above, it is an error for Unit1 and Unit2 to both define a global interface symbol named mySymbol

The individual units aggregated in a namespace are not available to source code unless the individual units are explicitly used in the file's uses clause. In other words, if a source file uses only the namespace, then fully qualified identifier expressions referring to a symbol in a unit in that namespace must use the namespace name, not just the name of the unit that defines that symbol. 

A uses clause may refer to a namespace as well as individual units within that namespace. In this case, a fully qualified expression referring to a symbol from a specific unit listed in the uses clause may be referred to using the actual unit name or the fully-qualified name (including namespace and unit name) for the qualifier. The two forms of reference are identical and refer to the same symbol.

Note: Explicitly using a unit in the uses
clause will only work when you are compiling from source or dcu files. If the namespace units are compiled into an assembly and the assembly is referenced by the project instead of the individual units, then the source code that explicitly refers to a unit in the namespace will fail.

Copyright(C) 2008 CodeGear(TM). All Rights Reserved.
What do you think about this topic? Send feedback!