IronPython: clr.AddReference

In order to interop with .NET libraries, we need first load in the assemblies we want to play with. The family of AddReference methods in the clr module serves the purpose.

  • clr.AddReference
  • clr.AddReferenceByName
  • clr.AddReferenceByPartialName
  • clr.AddReferenceToFile
  • clr.AddReferenceToFileAndPath

clr.AddReference accepts System.Reflection.Assembly objects and/or assembly names. The typical usage looks like:

asm = ... # any approach of getting an Assembly object
clr.AddReference("System.Drawing, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")

If a string is passed in, Assembly.Load(string) is used to load the assembly. If failed, it re-tries with Assembly.LoadWithPartialName(string).

clr.AddReferenceByName basically is the wrapper of Assembly.Load, which means the assembly full name is expected. Similarly, clr.AddReferenceByPartialName is the wrapper around the obsolete Assembly.LoadWithPartialName: "System.Drawing" and "System.Drawing, Version=" are examples of valid arguments.

A typical usage of clr.AddReferenceToFile looks like:

clr.AddReferenceToFile("my1.dll", "my2")  # load my1.dll and my2.dll

It expects file name(s) without the directory path as the argument. To locate my1.dll, it searches each directory in sys.path until the specified file is found (it also tries to append ".dll" or ".exe"). Behind the scene, the loading is via Assembly.LoadFile. Normally sys.path includes the current working directory; so if my1.dll and my2.dll exist in the current directory, clr.AddReferenceToFile("my1.dll", "my2") should succeed. If you know the exact full path of a clr assembly, you may use clr.AddReferenceToFileAndPath to load. As a by-product, the path of the assembly file will be appended to sys.path. Note Assembly.LoadFrom is never used underneath by these 5 methods due to the loading context concern.

We can use clr.References to list which assemblies have been loaded. The output shows that mscorlib.dll and System.dll are implicitly loaded/added (without the need of calling clr.AddReference).

>>> import clr
>>> for r in clr.References: print r
mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089
System, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089
>>> clr.AddReference("System.Drawing")
>>> for r in clr.References: print r
mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089
System, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Drawing, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

By treating the namespace like the python module, IronPython extends the import statement semantics to bring in the clr namespace. In current implementation the "classical" python module is still preferred by import: given the statement "import MyLib", if the file "" is present under any directory of sys.path, that file will be imported; if not, for each loaded assembly, import tries to find the namespace "MyLib". Although not common in well-designed .NET frameworks, a type could have empty namespace; so "import MyLib" also tries to find type "MyLib" in the loaded assemblies. 

The namespace of "MyLib" could exist more than 1 loaded assemblies. The following snippet shows that, "import System" adds a CLS module to sys.modules and this CLS module contains members from 2 assemblies: mscorlib.dll and System.dll (since both assemblies has types and down-level namespaces like System.*). 

> ipy.exe
>>> import sys
>>> sys.modules.keys()
['sys', '__builtin__', '__main__', 'site']
>>> import System
>>> sys.modules.keys()
['sys', '__builtin__', '__main__', 'site', 'System']
>>> sys.modules['System']
<module 'System' (CLS module, 2 assemblies loaded)>
>>> System == sys.modules['System']    # better use "is" to check identity as Michael pointed out in the comment
>>> System.__file__
['mscorlib, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089', 'System, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089']
>>> System.Console.WriteLine('later')