Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 183 additions & 35 deletions dotnet-interface-guide/docs/dotnet-classes/advanced-techniques.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,40 +159,117 @@ For example, if <code class="language-nonAPL">MyClass</code> is a .NET class wit

## Generics

The .NET interface supports creating concrete versions of _generic classes_, instantiating them, and calling generic methods. This is done by calling `43⌶` with a right argument of `632` (for more information on `43⌶`, see the _Dyalog APL Language Reference Guide_). Note that this I-beam will be replaced with something that is better integrated into the language in a future Dyalog version.
In .NET, a method, interface, or class can be _generic_, which means it it is a template or recipe for a _concrete_ method, interface or class. What makes them generic is that they have a list of type parameters, and the user must apply a matching number of type arguments to get a concrete version. In the case of methods, it is not always necessary to apply type arguments, as the .NET interface can sometimes perform [type inference](#type-inference) to deduce the type arguments from the types of the method arguments.
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated

A generic class is a class that has type parameters, which must be given values to create a concrete version of the class. Similarly, a generic method has type parameters that must be specified before the method can be called.
The syntax used to apply type arguments to both methods, classes, and interfaces, is the square brackets, as in `G[T]` where `G` is the generic thing, and `T` is a .NET type, or a vector of .NET types, which themselves might be the result of applying types to a generic .NET class.
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated

The result of `43⌶632` is a monadic operator, which is used to apply the type arguments of generic classes or methods.
The square bracket syntax means that working with generics in Dyalog APL and C# looks visually similar, except that C# uses angle brackets instead, as illustrated by the example below:
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated

```C#
// Instantiate a concrete version of a generic class in C#
new System.Collections.Generic.List<System.Int32>();

// Call a concrete version of a generic method in C#
System.Decimal.CreateChecked<System.Int32>(5);
```

And the corresponding APL
```apl
⍝ Instantiate a concrete version of a generic class in APL
⎕NEW System.Collections.Generic.List[System.Int32]

⍝ Call a concrete version of a generic method in APL
System.Decimal.CreateChecked[System.Int32] 5
```

### Creating a Concrete Version of a Generic Class

The class <code class="language-nonAPL">System.Collections.Generic.List</code> is a generic class with one type parameter, which is the type of the elements of the list.
The class <code class="language-nonAPL">System.Collections.Generic.List</code> is a generic class with one type parameter, which is the type of the elements of the list. The display form of the type indicates that it is generic:

A concrete version of the <code class="language-nonAPL">List</code> class can be created using `43⌶632`. For example, a list class that contains integers can be created as follows:
```apl
IntList←System.Collections.Generic.List(43⌶632)System.Int32
IntList
(System.Collections.Generic.List`1[System.Int32])
⎕USING←''
System.Collections.Generic.List
(System.Collections.Generic.List[T])
```

A concrete version of the <code class="language-nonAPL">List</code> class can be created using square brackets. For example, a list class that contains integers can be created as follows:
```apl
⎕USING←''
IntList←System.Collections.Generic.List[System.Int32]
IntList
(System.Collections.Generic.List[System.Int32])
```

The shared members of the <code class="language-nonAPL">IntList</code> class can then be accessed, and the class instantiated using `⎕NEW`. It is not necessary to give the constructed class a name.

The operations can also be combined and multiple type argument specified. For example:
The operations can also be combined and multiple type arguments specified. For example:
```apl
types←System.Char System.Int32
⎕NEW System.Collections.Generic.Dictionary (43⌶632) types
⎕USING←''
types←System.Char System.Int32
⎕NEW System.Collections.Generic.Dictionary[types]
System.Collections.Generic.Dictionary`2[System.Char,System.Int32]
```

Attempting to instantiate a generic class without the expected number of type arguments generates an error. For example:
```apl
⎕USING←''
⎕NEW System.Collections.Generic.List
LENGTH ERROR: No overload of the type expects the given number of generic type arguments: 0
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated
⎕NEW System.Collections.Generic.List
```

Attempting to instantiate a generic class without the expected number of type arguments generates an exception. For example:
Similarly, applying too many type arguments also results in an error:

```apl
⎕USING←''
⎕NEW System.Collections.Generic.List
EXCEPTION: Invalid number of generic type arguments applied (expected 1, got 0)
⎕NEW System.Collections.Generic.List
⎕USING←''
System.Collections.Generic.List[3⍴System.Int32]
LENGTH ERROR: No overload of the type expects the given number of generic type arguments: 3
System.Collections.Generic.List[3⍴System.Int32]
```
### Creating a Concrete Version of a Generic Interface

Applying type arguments to generic interfaces looks just like with generic classes. The example below defines a function `IsBoolCollection`, which checks whether a given .NET type implements the concrete version `ICollection[Boolean]` of the generic `ICollection` interface, which is often implemented by data structures that act as collections of elements of a specific type.

```apl
⎕USING←'System' 'System.Collections.Generic'

IsBoolCollection←{ICollection[Boolean]∊∊⎕CLASS ⍵}

a←⎕NEW HashSet[Int32]
b←⎕NEW List[Boolean]
c←⎕NEW Dictionary[Int32 Boolean]

IsBoolCollection¨a b c
0 1 0
```

### Multiple Overloads of .NET Classes and Interfaces
Some .NET classes and interfaces have multiple overloads, varying in the number of generic type parameters. The display form of the type makes this clear, and the the .NET interface will automatically use the appropriate overload based on context.
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated

```apl
⎕USING←'System'
ValueTuple
(System.ValueTuple)
(System.ValueTuple[T1])
(System.ValueTuple[T1,T2])
(System.ValueTuple[T1,T2,T3])
(System.ValueTuple[T1,T2,T3,T4])
(System.ValueTuple[T1,T2,T3,T4,T5])
(System.ValueTuple[T1,T2,T3,T4,T5,T6])
(System.ValueTuple[T1,T2,T3,T4,T5,T6,T7])
(System.ValueTuple[T1,T2,T3,T4,T5,T6,T7,TRest])
```

The <code class="language-nonAPL">ValueTuple</code> class has one non-generic overload, and 8 generic overloads.

```apl
⎕USING←'System'
ValueTuple[Int32] ⍝ Create concrete version of overload with 1 generic parameter
(System.ValueTuple[System.Int32])

ValueTuple[Int32 Boolean] ⍝ Create concrete version of overload with 2 generic parameters
(System.ValueTuple[System.Int32,System.Boolean])
```

### Calling a Generic Method
Expand All @@ -206,34 +283,105 @@ System.Decimal CreateChecked[TOther](TOther)

The <code class="language-nonAPL">CreateChecked</code> function has one type parameter, shown in square brackets, and one regular parameter, shown in parentheses.

The generic type argument can be applied using `43⌶632`. When the monadic operator `43⌶632` is run with a .NET method as its left argument, and a scalar type or a vector of .NET types as its right argument, the result is a derived function that calls the specified .NET method with both the generic type arguments and regular arguments applied. For example:
The generic type argument can be applied using square brackets, and the result is concrete version of the generic method. The method can either be given a name, or evaluated directly. The display form indicates that the type parameters have been replaced by a concrete type.
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated

```apl
fn←System.Decimal.CreateChecked (43⌶632) System.Int32
fn 123
123
⎕USING←'System'
fn←Decimal.CreateChecked[Int32]
fn
System.Decimal CreateChecked[Int32](Int32)
fn 10
10
Decimal.CreateChecked[Int32] 50
50
```

Attempting to call a method without specifying the type argument generates an exception. For example:
If a generic method has overloads with different numbers of type parameters, apply type arguments will narrow down the list of overloads which are applicable, as in the example below where only one overload expects a single type argument.
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated

```apl
System.Decimal.CreateChecked 123
EXCEPTION: Invalid number of generic type arguments applied (expected 1, got 0)
System.Decimal.CreateChecked 123
⎕USING←'System'
ValueTuple.Create
System.ValueTuple Create()
System.ValueTuple`1[T1] Create[T1](T1)
System.ValueTuple`2[T1,T2] Create[T1,T2](T1, T2)
System.ValueTuple`3[T1,T2,T3] Create[T1,T2,T3](T1, T2, T3)
...

ValueTuple.Create[Boolean]
System.ValueTuple`1[System.Boolean] Create[Boolean](Boolean)
```

### Calling a Niladic Generic Method
.NET methods with only a single overload which expects no arguments, are usually imported into APL as niladic functions, but when they are generic, they are imported as monadic functions so that the type arguments can be applied.

.NET methods that only export overloads with zero parameters are exported as niladic functions, which means they cannot be passed as an operand to `43⌶632` without being evaluated. To accomodate this, `43⌶632` also accepts a character vector as its left argument. In this case, it runs the .NET method with that name, with the generic type arguments applied. For example:
```apl
r←'System.Array.Empty'(43⌶632)System.Int32
r≡⍬
⎕USING←'System'
Array.Empty
T[] Empty[T]()

Array.Empty[Int32]
Int32[] Empty[Int32]()

r←Array.Empty[Int32] ⍬
r≡⍬
1
```

Attempting to evaluate the niladic function without calling `43⌶632` generates an exception. For example:
Applying an incorrect number of type arguments to a method will generate an error:

```apl
System.Array.Empty
EXCEPTION: Invalid number of generic type arguments applied (expected 1, got 0)
System.Array.Empty
⎕USING←'System'
Decimal.CreateChecked 50
LENGTH ERROR: No overload of the method expects the given number of generic type arguments: 0
Decimal.CreateChecked 50

ValueTuple.Create[10⍴Int32]
LENGTH ERROR: No overload of the method expects the given number of generic type arguments: 10
ValueTuple.Create[10⍴Int32]
```

However, in some cases [type inference](#type-inference) can take place, and no error will be generated.

#### Type Inference

If the arguments to a generic method have a concrete .NET type, then their type information might be enough for the .NET bridge to unambiguously select a method overload, and to automatically apply the needed type arguments. But if there is any doubt about the type, such as when the arguments are regular APL arrays, such as the scalar `0`, which can be converted into a number of different .NET types, type inference will not take place.
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated

```
⎕USING←'System' 'System.Threading.Tasks'
Task.FromResult
System.Threading.Tasks.Task`1[TResult] FromResult[TResult](TResult)

Task.FromResult 123
LENGTH ERROR: No overload of the method expects the given number of generic type arguments: 0
Task.FromResult 123

⍝ Explicitly apply type arguments
Task.FromResult[Int128] 123
System.Threading.Tasks.Task`1[System.Int128]

⍝ Explicitly apply type arguments, but pass in a .NET object
i128←Int128.Parse ⊂'123'
Task.FromResult[Int128] i128
System.Threading.Tasks.Task`1[System.Int128]

⍝ Let the bridge infer the type argument from argument's .NET type
Task.FromResult i128
System.Threading.Tasks.Task`1[System.Int128]
```

Type inference can save the programmer from doing some typing which feels unnecessary, as shown in the last lines of the example above, but it is still allowed to manually apply type arguments.
Comment thread
pmikkelsen marked this conversation as resolved.
Outdated

If the user has [specified an overload](#specifying-overloads), the type information is taken into account, which means another way of doing the above would be:

```apl
⎕USING←'System' 'System.Threading.Tasks'
Task.FromResult
System.Threading.Tasks.Task`1[TResult] FromResult[TResult](TResult)

Task.FromResult⍠Int128⊢123
System.Threading.Tasks.Task`1[System.Int128]
```

which works because we tell the .NET bridge that we want the overload that takes an <code class="language-nonAPL">Int128</code> as its argument, which makes the type parameter `TResult` *must* be <code class="language-nonAPL">Int128</code>, and therefore there is no need to explicitly apply the type arguments using square brackets.