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
.
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.
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"))
julia> using DotNET
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 CLRObject
s 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 CLRObject
s, 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)
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 Array
s 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}:
1
⋮
8
Or just use the for-in
loop:
for x in arr
println(x)
end
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.
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]")
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