# 14. Using .NET

> Chapter 14: use any .NET type from Alice, add NuGet packages, reference C# projects, and publish Alice libraries that C# and F# call with .NET names.

Canonical: https://alice-lang.org/documentation/learn/dotnet/

[Documentation index](https://alice-lang.org/documentation/llms.txt) · [Complete book](https://alice-lang.org/llms-full.txt)

A new language usually starts with an empty library shelf. Alice does not: it runs on .NET, so every class of the .NET base library and every package on NuGet is available from the first line. This chapter shows how the two worlds meet, in both directions.

## 14.1 .NET types are Alice types

You have used .NET already: `Random`, `Math`, `List(T)`, `Stopwatch`. There is no wrapper layer. An Alice `Int32` *is* a .NET `int`, an `Array(T)` is a `T[]`, a `Hash(K, V)` is a `Dictionary<K, V>`. So values pass between Alice and .NET code without conversion.

```alice
using System.Text

sb = StringBuilder.new
sb.Append("cups: ").Append(3)
puts sb.ToString

today = DateTime.new(2026, 11, 2)
puts today.ToString("yyyy-MM-dd")
puts today.AddDays(30).DayOfWeek
puts System.IO.Path.Combine("menu", "teas.txt")
puts Math.Round(2.567, 2)
```

```text title="Output"
cups: 3
2026-11-02
Wednesday
menu/teas.txt
2.57
```

The rules are short:

- `using System.Text` imports a namespace, like C#'s `using`. A full name such as `System.IO.Path` works without it.
- `Type.new(args)` calls a constructor.
- Members keep their .NET names: `Append`, `ToString`, `AddDays`. Parentheses are optional when there are no arguments.
- Generic types use parentheses: `List(String)`, `Dictionary(String, Int32)`.

Many members also answer to the Alice spelling: `sb.to_s`, `Console.write_line`, `dict.contains_key?(k)`. Where an Alice method of the same name exists, such as `split` or `sum`, the Alice method wins.

### Delegates and LINQ

.NET methods that take a function, like LINQ's `Where` and `Select`, take an Alice lambda:

```alice
evens = (1..10).to_a
  .Where(->(x : Int32) { x.even? })
  .Select(->(x : Int32) { x * x })
  .ToArray()
puts evens
```

```text title="Output"
[4, 16, 36, 64, 100]
```

Alice's own `select` and `map` usually read better and compile to faster loops ([chapter 4](https://alice-lang.org/documentation/learn/collections.md)). Use LINQ where a .NET API expects it, such as Entity Framework queries.

### `out` parameters

Some .NET methods return extra results through `out` parameters. Pass a variable with `out`:

```alice
cups = 0
if Int32.TryParse("42", out cups)
  puts cups + 1
end
```

```text title="Output"
43
```

### JSON with the standard library

`System.Text.Json` works with Alice records directly:

```alice
using System.Text.Json

record Order(tea : String, cups : Int32)

json = JsonSerializer.Serialize(Order.new("Assam", 2))
puts json
back = JsonSerializer.Deserialize<Order>(json)
puts back
```

```text title="Output"
{"tea":"Assam","cups":2}
Order { tea = Assam, cups = 2 }
```

`Deserialize<Order>` passes the type argument to a generic .NET method, as in C#. For quick scripts, `parse_json(text)` returns a dynamically typed document you can index with `data["tea"]`.

## 14.2 NuGet packages

NuGet is .NET's package registry. Add a package to a project with `alice add`:

```sh
alice add Newtonsoft.Json --version '13.*'
```

The dependency goes into `alice.toml`, and the resolved version into `alice.lock`:

```toml
[dependencies]
"Newtonsoft.Json" = "13.*"
```

Then use it like any .NET library:

```alice skip="needs the Newtonsoft.Json NuGet package in a project"
using Newtonsoft.Json

record Greeting(message : String)

puts JsonConvert.SerializeObject(Greeting.new("Hello, Alice!"))
```

Packages need a project: a single file run with `alice run file.al` sees only the .NET base library. NuGet packages are cached and shared with C# projects on the same machine.

## 14.3 C# and Alice in one solution

Large .NET codebases mix languages. Alice fits in both directions.

**Alice using a C# project.** Add it as a path dependency:

```toml
[dependencies]
CSharpLib = { path = "../CSharpLib/CSharpLib.csproj" }
```

Its public types are then available by name, with their XML documentation in your editor.

**C# using an Alice library.** Create a library package:

```sh
alice new Geometry --lib
```

A C# project references the generated MSBuild project, after one `alice build` has created it:

```xml
<ProjectReference Include="../Geometry/.alice/Geometry.alproj" />
```

### Names that feel native: `naming = "dotnet"`

Alice methods are named `total_area` and `empty?`. C# programmers expect `TotalArea` and `IsEmpty`. A library can publish .NET names with one line in its manifest; `alice new --lib` writes it for you:

```toml
[package]
name = "Geometry"
kind = "lib"
naming = "dotnet"
```

Public members then compile to .NET names: `total_area` becomes `TotalArea`, `empty?` becomes `IsEmpty`, and record fields become properties in PascalCase. Alice code keeps calling the Alice spelling, because Alice resolves both. F# callers can also match the library's sum types.

The repository's [interop samples](https://github.com/alicelanguage/alice/tree/main/samples) show an Alice library used from C#, and a C# library used from Alice, built and run by its test suite.

## 14.4 Beyond the basics

A few more .NET features have direct Alice forms. The [.NET guide](https://alice-lang.org/documentation/guides/dotnet.md) covers them in detail:

- **Inheriting .NET classes** and overriding virtual methods: `class MyList < List(Int32)`.
- **Events and delegates**: `event clicked : EventHandler`, `delegate Op(a : Int32, b : Int32) : Int32`.
- **Source generators** run on Alice code, for example `[GeneratedRegex]` and `[JsonSerializable]`, which give regex and JSON code that needs no reflection and works with NativeAOT.
- **Shared frameworks** such as ASP.NET Core need a framework reference ([chapter 19](https://alice-lang.org/documentation/learn/applications.md)).

## Project: the menu as a JSON file

The shop's menu moves from code into a file that the manager can edit. The program writes it with `System.Text.Json`, reads it back, and uses the .NET file API:

```alice
using System.Text.Json

record Tea(name : String, cents : Int32)

menu = [Tea.new("Assam", 250), Tea.new("Sencha", 350)]
path = System.IO.Path.Combine(System.IO.Path.GetTempPath, "tea-shop-menu.json")
File.WriteAllText(path, JsonSerializer.Serialize(menu))

loaded = JsonSerializer.Deserialize<Array(Tea)>(File.ReadAllText(path))
unless loaded.nil?
  loaded.each { |t| puts "#{t.name}: #{t.cents}" }
end
File.Delete(path)
```

```text title="Output"
Assam: 250
Sencha: 350
```

`Deserialize` returns `nil` for the JSON text `null`, so its type is nilable, and the compiler asks for the check. For values that come from generic .NET methods, write the check as `unless x.nil?`: the short forms `if x` and `x || default` do not work on them yet.

:::caution\[Common mistakes]

- **`using` a NuGet package without adding it.** `using` only imports names. Run `alice add PACKAGE` in a project first.
- **Expecting packages in single-file mode.** `alice run file.al` sees the base library only. Create a project for dependencies.
- **Editing `.alice/*.alproj`.** It is regenerated from `alice.toml` on every command.
- **Mixing Alice and .NET collection methods by accident.** `xs.select` is Alice's iterator; `xs.Where` is LINQ. Both work; Alice's is usually faster.
  :::

## Exercises

1. Use `System.Text.StringBuilder` to build the string `"1,2,3,4,5"` in a loop, then print it.
2. Use `Int32.TryParse` to print the sum of the numbers in `["4", "x", "10"]`, skipping text that is not a number.
3. Serialize a list of two `Order` records to JSON and print the text.

<details>
<summary>Answers</summary>

1. Answer:

   ```alice
   sb = System.Text.StringBuilder.new
   (1..5).each do |i|
     sb.Append(",") if i > 1
     sb.Append(i)
   end
   puts sb.ToString
   ```

   ```text title="Output"
   1,2,3,4,5
   ```

2. Answer:

   ```alice
   total = 0
   ["4", "x", "10"].each do |text|
     n = 0
     total += n if Int32.TryParse(text, out n)
   end
   puts total
   ```

   ```text title="Output"
   14
   ```

3. Answer:

   ```alice
   using System.Text.Json

   record Order(tea : String, cups : Int32)

   orders = [Order.new("Assam", 2), Order.new("Sencha", 1)]
   puts JsonSerializer.Serialize(orders)
   ```

   ```text title="Output"
   [{"tea":"Assam","cups":2},{"tea":"Sencha","cups":1}]
   ```

</details>

## Summary

- Every .NET type is an Alice type. Import namespaces with `using`, construct with `.new`, call members by their .NET names.
- Lambdas pass where .NET expects delegates; `out` arguments are written `out x`.
- `alice add` adds NuGet packages to a project; `alice.lock` pins versions.
- C# projects are path dependencies; C# references an Alice library through its generated `.alproj`.
- `naming = "dotnet"` publishes PascalCase .NET names for C# and F# callers.

[Chapter 15](https://alice-lang.org/documentation/learn/async.md) covers waiting: async code, channels and actors.

## Continue reading

- Previous: [13. Rules and proofs](https://alice-lang.org/documentation/learn/proofs.md)
- Next: [15. Async code and actors](https://alice-lang.org/documentation/learn/async.md)
