Popularity
78 Stars
Updated Last
1 Year Ago
Started In
December 2019

DotNET.jl

Build Status PRs Welcome

This package provides interoperability between Julia and Common Language Runtime, the execution engine of .NET applications. Many languages run on CLR, including C#, Visual Basic .NET and PowerShell.

Prerequisites

  • Julia version 1.3+
  • .NET Core Runtime version 2.2+ (Download)

WinForms and other GUI-related features require a desktop runtime.

If the package fails to locate the runtime, set DOTNET_ROOT environment variable to the path containing the dotnet or dotnet.exe binary.

.NET Framework is not supported yet.

Installation

In the REPL, type ]add DotNET and press Enter.

(v1.x) pkg> add DotNET

Or use Pkg.add for more options:

julia> using Pkg

julia> Pkg.add(PackageSpec(url = "https://github.com/azurefx/DotNET.jl"))

Usage

julia> using DotNET

Types and Objects

DotNET.jl provides the T"AssemblyQualifiedTypeName" literal for type reference:

julia> Console = T"System.Console, mscorlib"
System.Console

Given a type object, you can access its properties or methods using the dot operator:

julia> Console.WriteLine("Hello from .NET!");
Hello from .NET!

To create an object, use the new syntax:

julia> T"System.Guid".new("CA761232-ED42-11CE-BACD-00AA0057B223")
System.Guid("ca761232-ed42-11ce-bacd-00aa0057b223")

All .NET objects are represented by CLRObjects in Julia, including types:

julia> typeof(Console)
CLRObject

julia> typeof(null)
CLRObject

null is a built-in object that does not refer to a valid .NET object. When you try to access a member on a null value, a System.NullReferenceException is thrown.

Arguments passed to .NET methods are automatically converted to CLRObjects, and return values are converted to corresponding Julia types:

julia> T"System.Convert".ToInt64("42")
42

Or you could do some explicit conversions:

julia> s = convert(CLRObject, "")
System.String("")

julia> DotNET.unbox(s)
""

To pass an argument by reference (out/ref in C#), wrap it into a Ref object:

julia> result = Ref{Int}()
Base.RefValue{Int64}(212700848)

julia> T"System.Int64".TryParse("1970", result)
true

julia> result[]
1970

julia> result = Ref(null)
Base.RefValue{CLRObject}(null)

julia> T"System.Int64".TryParse("2022", result)
true

julia> result[]
System.Int64(2022)

Arrays and Collections

To copy a multidimensional array from .NET to Julia, use collect method:

julia> arr = convert(CLRObject, reshape(1:8, 2, 2, 2))
System.Int64[,,]("System.Int64[,,]")

julia> collect(arr)
2×2×2 Array{Int64, 3}:
[:, :, 1] =
 1  3
 2  4

[:, :, 2] =
 5  7
 6  8

CLI Array elements are stored in row-major order, thus the equivalent definition in C# is

public static long[,,] Get3DArray() {
  return new long[2, 2, 2] {
    {{1, 2}, {3, 4}},
    {{5, 6}, {7, 8}}
  };
}

To index into arrays, use arraystore and arrayload methods. Note that CLI Arrays use zero-based indexing.

julia> DotNET.arraystore(arr, (1, 1, 1), 0)
null

julia> DotNET.arrayload(arr, (1, 1, 1)) == collect(arr)[2, 2, 2]
true

If an object implements IEnumerable interface, you can call GetEnumerator to iterate over the array:

julia> ch = Channel() do it
           e = arr.GetEnumerator()
           while e.MoveNext()
               put!(it, e.Current)
           end
       end
Channel{Any}(0) (1 item available)

julia> collect(ch)
8-element Vector{Any}:
 18

Or just use the for-in loop:

for x in arr
    println(x)
end

Loading External Assemblies

If you have a DLL file, you can load it using reflection:

julia> T"System.Reflection.Assembly".LoadFrom(raw"C:\Users\Azure\Desktop\test.dll")
System.Reflection.RuntimeAssembly("test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")

Now you have access to types defined in the assembly.

Generics

Generic types are be expressed in the following ways:

julia> ListT = T"System.Collections.Generic.List`1"
System.Collections.Generic.List`1[T]

julia> ListInt64 = T"System.Collections.Generic.List`1[System.Int64]"
System.Collections.Generic.List`1[System.Int64]

The number 1 after the backtick indicates the type System.Collections.Generic.List<T> has one type parameter. ListT has a free type variable, just like Vector{T} where T in Julia. A type that includes at least one type argument is called a constructed type. ListInt64 is a constructed type.

One can substitute type variables and make a constructed type by calling makegenerictype method:

julia> DotNET.makegenerictype(ListT, T"System.String")
System.Collections.Generic.List`1[System.String]

To invoke a generic method, put type arguments into square brackets:

julia> list = ListT.new[T"System.Int64"]()
System.Collections.Generic.List`1[System.Int64]("System.Collections.Generic.List`1[System.Int64]")

Delegates

To create a delegate from a Julia method, use delegate method:

julia> list = ListT.new[T"System.Int64"](1:5)
System.Collections.Generic.List`1[System.Int64]("System.Collections.Generic.List`1[System.Int64]")

julia> list.RemoveAll(delegate(iseven, T"System.Predicate`1[System.Int64]"))
2

julia> collect(list)
3-element Vector{Int64}:
 1
 3
 5