Skip to content
Open
Changes from all commits
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
220 changes: 184 additions & 36 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 that 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 create 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.

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 methods, classes, and interfaces, is square brackets, for example `G[T]` where `G` is the generic entity, and `T` is a .NET type, or a vector of .NET types. These types themselves might be the result of applying types to a generic .NET class.

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, as illustrated by the example below:

```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])
```

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.
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 operations can also be combined and multiple type argument specified. For example:
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 before creating instances of it. Multiple type arguments can also be 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 (0) of generic type arguments
⎕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 (3) of generic type arguments
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 .NET interface will automatically use the appropriate overload based on context.

```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 a 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 to form a concrete function.

```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, applying 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.

```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 (0) of generic type arguments
Decimal.CreateChecked 50

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

However when no type arguments are applied, 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, for example the scalar `0`, which can be converted into a number of different .NET types, type inference will not take place.

```
⎕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 (0) of generic type arguments
Task.FromResult 123

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

⍝ Explicitly apply type arguments, and pass in a .NET object of that type
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 unnecessary typing, as shown in the last lines of the example above, but it is still allowed to manually apply type arguments.

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 means 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.