Udonite compiles a large slice of everyday C#. Every construct below is covered by tests that assemble the output and run it on the real Udon virtual machine.

## What compiles

### Types and members

<div class="chips">
<span class="tag"><code>class</code></span>
<span class="tag"><code>struct</code></span>
<span class="tag"><code>interface</code></span>
<span class="tag"><code>enum</code></span>
<span class="tag"><code>record</code> (one declaration)</span>
<span class="tag">nested types</span>
<span class="tag">your own classes as fields and locals</span>
<span class="tag">generics with constraints</span>
<span class="tag">default interface methods</span>
<span class="tag">properties and indexers</span>
<span class="tag">extension methods</span>
<span class="tag">optional and named arguments</span>
<span class="tag"><code>params</code></span>
<span class="tag"><code>out</code> and <code>ref</code> parameters</span>
<span class="tag">tuples and deconstruction</span>
<span class="tag"><code>typeof</code> as a value</span>
<span class="tag">local functions</span>
<span class="tag">static helper classes</span>
</div>

Records: positional construction, `with`, value equality, `ToString`, deconstruction. They need one declaration in your own scripts first, because every `record` and every `init` setter compiles to init-only properties, and Unity builds world code against a .NET version whose class library has no `System.Runtime.CompilerServices.IsExternalInit`. Without it Unity's own compiler stops the build with `CS0518` before Udonite runs:

```csharp
namespace System.Runtime.CompilerServices
{
    internal static class IsExternalInit { }
}
```

`record struct` and `with` on a struct are C# 10, and Unity compiles at C# 9, so those do not work at all. Static fields must be `const` or `readonly` (see below).

### Inheritance

<div class="chips">
<span class="tag"><code>abstract</code> classes</span>
<span class="tag"><code>abstract</code> members</span>
<span class="tag"><code>virtual</code> and <code>override</code></span>
<span class="tag"><code>base.</code> calls</span>
<span class="tag">abstract behaviours</span>
</div>

A call through a base type reaches the most derived override, and `base.Method()` reaches exactly one level up. This holds for plain classes and for behaviours sitting on GameObjects.

An abstract base class is how components share a contract, because Unity cannot serialize an interface field. See [Interfaces](/docs/compiler/interfaces).

### Statements and expressions

<div class="chips">
<span class="tag">control flow</span>
<span class="tag">switch expressions</span>
<span class="tag">pattern matching</span>
<span class="tag">null operators</span>
<span class="tag">string interpolation</span>
<span class="tag">arithmetic, bitwise and shift operators</span>
<span class="tag">flags enums</span>
<span class="tag">arrays and multidimensional arrays</span>
<span class="tag">ranges and indices</span>
<span class="tag"><code>Array</code> helpers</span>
<span class="tag"><code>List&lt;T&gt;</code></span>
<span class="tag"><code>Dictionary&lt;K,V&gt;</code></span>
<span class="tag"><code>HashSet&lt;T&gt;</code></span>
<span class="tag"><code>Queue&lt;T&gt;</code></span>
<span class="tag"><code>Stack&lt;T&gt;</code></span>
<span class="tag">LINQ</span>
<span class="tag">delegates and events</span>
<span class="tag">lambdas</span>
<span class="tag">strings and <code>StringBuilder</code></span>
<span class="tag">nullable value types</span>
<span class="tag">enum parsing and names</span>
<span class="tag"><code>try</code> / <code>finally</code></span>
<span class="tag"><code>throw</code></span>
<span class="tag">recursion</span>
</div>

Control flow is `if`, `switch`, `for`, `foreach`, `while`, `do`, `break` and `continue`. Pattern matching covers type patterns, `is not null`, relational patterns such as `is > 5 and < 10`, property and positional patterns, and `when` guards. The null operators are `?.`, `??` and `??=`. Interpolated strings take format specifiers, and `nameof` compiles. Ranges and indices cover `arr[^1]`, `arr[1..3]` and the same forms on a string: `text[1..3]`, `text[^2..]`, `text[..^1]`. A `typeof(T)` is a value you can hold in a local, a field, a parameter or an array, compare with `==` and `Equals`, and ask `IsAssignableFrom` and `IsInstanceOfType`; `typeof(T).Name` and `.FullName` are folded to strings at compile time. The `Array` helpers are `Sort`, `Find`, `FindAll`, `FindLast`, `Exists`, `IndexOf`, `LastIndexOf`, `Resize`, `Copy`, `Fill` and `ConvertAll`.

LINQ works over arrays, lists and dictionaries: `Select`, `Where`, `OrderBy`, `ThenBy`, `GroupBy`, `First`, `Any`, `All`, `Sum`, `Average`, `Min`, `Max`, `Count`, `Take`, `Skip`, `TakeWhile`, `SkipWhile`, `Distinct`, `Reverse`, `Concat`, `Zip`, `Union`, `Intersect`, `Except`, `SequenceEqual`, `ToArray`, `ToList`, `ToDictionary`, `Range`, `Repeat`, and chains of them. Lambdas passed to LINQ and to `List<T>`/`Array` helpers are inlined at the call site.

Delegates are `Action` and `Func` fields, method groups such as `Action done = Cleanup;`, multicast `+=` and `-=`, C# `event` declarations, and delegates to a public method on another behaviour. Lambdas work as values; one that captures a variable has one restriction, covered below. Strings have `Format`, `Split`, `Join`, `Substring`, `Replace`, `Trim` and `PadLeft`. Nullable value types have `HasValue` and `GetValueOrDefault`. Enums have `Parse`, `TryParse`, `GetNames`, `GetValues` and `HasFlag`, and `typeof(T).Name` compiles. `throw` halts the behaviour with its message logged, so it works as an assertion; there is nothing to catch. Direct and mutual recursion compile, with a method's variables spilled to a stack around the call.

### Unity and VRChat

<div class="chips">
<span class="tag">component lookup</span>
<span class="tag"><code>Instantiate</code> and <code>Destroy</code></span>
<span class="tag">transforms and physics</span>
<span class="tag"><code>Mathf</code> and the value types</span>
<span class="tag"><code>Random</code> and <code>Time</code></span>
<span class="tag">coroutines</span>
<span class="tag"><code>Invoke</code> family</span>
<span class="tag"><code>async</code> and <code>await</code></span>
<span class="tag"><code>VRCPlayerApi</code></span>
<span class="tag"><code>Networking</code></span>
<span class="tag">pickups and stations</span>
<span class="tag"><code>UdonBehaviour</code> references</span>
<span class="tag">other behaviours' Unity members</span>
<span class="tag">arrays of Unity and SDK types</span>
<span class="tag">casts between Unity object types</span>
</div>

Component lookup is `GetComponent<T>()`, its variants, the non-generic `GetComponent(typeof(T))` and `TryGetComponent<T>(out T)`, plus `SetActive`. The value types are `Vector3`, `Quaternion`, `Color` and the rest of the common Unity structs. Coroutines use `yield return null`, `WaitForSeconds`, `WaitUntil` and nested coroutines, start with `StartCoroutine(Run())` or `StartCoroutine(nameof(Run))`, and stop with `StopCoroutine(handle)` or `StopAllCoroutines`. `Invoke`, `InvokeRepeating` and `CancelInvoke` work on public methods. `async` methods can return `void`, `Task`, `UniTask` or `UniTaskVoid`, with `await Task.Delay(...)` and awaiting other async methods. `UdonBehaviour` references carry `SendCustomEvent`, `SetProgramVariable` and `GetProgramVariable<T>`. Another behaviour's `gameObject`, `transform` and `enabled` read and write directly. Arrays of Unity types, components and SDK enums are fields like any other. Everything in [Networking](/docs/net).

### Writing to shaders and materials

<div class="chips">
<span class="tag"><code>VRCShader</code> globals</span>
<span class="tag"><code>material.SetFloat</code> and friends</span>
<span class="tag"><code>MaterialPropertyBlock</code></span>
<span class="tag"><code>EnableKeyword</code></span>
</div>

`UnityEngine.Shader` is not exposed. Globals go through `VRCShader.PropertyToID(name)` and
`VRCShader.SetGlobalFloat`, `SetGlobalVector`, `SetGlobalColor`, `SetGlobalMatrix`,
`SetGlobalTexture`, and the array forms of each. There is no `SetGlobalInt` — the SDK does not have
one.

**A global's name must start with `_Udon`.** VRChat ignores one that does not, and ignores it
silently: the call succeeds, the shader keeps its old value, and nothing is logged. Declare it in
the shader outside the `Properties` block, since a global is not a material property.

Material properties are ordinary: `SetFloat`, `SetColor`, `SetVector` and `GetColor` by name or by
id, `MaterialPropertyBlock`, and `EnableKeyword`. Prefer `sharedMaterial` — touching `material`
instantiates one at run time, and a runtime material is stripped from a build unless it is
registered on the scene descriptor.

Confirmed in an uploaded world on 2026-09-08, both routes, rather than only compiled.

## What Udon can never run

These are refused with `UDN0011`. The refusal says so, and says what to do instead.

| Construct | Why, and what to write instead |
|---|---|
| `catch` | Udon has no exceptions to catch: a fault halts the behaviour. Remove the `catch` and check the condition first. `try`/`finally` compiles, and `throw` halts the behaviour with its message logged. |
| Mutable `static` fields | Every behaviour has its own heap, so a static would be one value per behaviour rather than one shared value. Use a field on one behaviour and reference it, or a get-only `static` property, which has no storage to be wrong about (see [Singletons](/docs/compiler/singletons)). |
| `Awake`, `Reset`, `OnValidate`, `OnGUI`, `OnDrawGizmos`, `OnApplicationQuit`, `OnApplicationPause`, `OnApplicationFocus`, `OnAudioFilterRead` | Udon does not dispatch them. `Awake` work goes in `Start`; the rest are Editor-only or belong to an application rather than a world, and `OnPlayerLeft` is usually what `OnApplicationQuit` meant. |
| `CompareTag`, `gameObject.tag`, `FindGameObjectWithTag` | Udon does not expose tags. Compare layers or names, hold a reference, or use `GameObject.Find`. |
| `Camera.main` | Not exposed. Assign the camera to a field in the inspector. |
| `SendMessage`, `IsInvoking` and the other `MonoBehaviour` members Udon lacks | Coroutines, `Invoke` and `CancelInvoke` are lowered by Udonite; the rest have no version that runs in a world. |
| `Application.isPlaying`, `isFocused`, `targetFrameRate`, `platform`; `Time.timeScale`; `AudioListener` | Not exposed. A world is always playing, at real time. Use `Networking.LocalPlayer.IsUserInVR()` to tell VR from desktop, and set each `AudioSource` instead of the listener. |
| `PlayerPrefs`, `Resources.Load`, `SceneManager`, `UnityEvent`, `Shader`, `Input.mousePosition`, `new GameObject()`, `GameObject.CreatePrimitive`, `Mesh.CombineMeshes`, `TMP_Text.SetText` | Each refusal names the VRChat way: `PlayerData` for persistence, an inspector field for assets, `VRCPlayerApi.TeleportTo` instead of scene loading, an inspector-wired public method instead of a `UnityEvent`, `VRCShader` for global shader properties, head tracking data instead of the pointer, a prefab or template object to instantiate, meshes combined in the Editor, and the `text` property. |
| A delegate pointing at a method on a plain object (not a behaviour), and a delegate in a public field Unity would serialize | Neither survives the trip into a world. |
| A capturing lambda kept beyond the method that created it (assigned to a field, returned, or passed as an argument) | the captured variables live in the behaviour's single heap, so a stored closure would see stale state on the next call. Non-capturing lambdas can be stored freely; capturing ones work as locals called within the same method. |
| `checked` arithmetic | Udon's operator externs never raise on overflow, and Udon has no exceptions for one to raise. Test the operands before the operation instead. |
| An inspector-serialized field, or array, whose type is a class this program compiles | An instance exists only while the program runs, so Unity has nothing to put in the inspector and nothing to save: the field would arrive in the world empty. Make it private or `[NonSerialized]` and fill it in `Start`. This is about the field, not the type — the class itself works fine as a local, a parameter and a private field. Fields and arrays of primitives, Unity types, SDK enums and behaviour references are fine. |
| Recursion through a delegate (`UDN0012`) | Direct and mutual recursion compile. A cycle that passes through a delegate call has no known target to spill for, so call the method directly inside the cycle. |

## Not yet

Refused with `UDN0002`. These are gaps in Udonite rather than in Udon, and the ones people ask for get lifted.

- Capturing lambdas declared inside loops.

If a refusal blocks you, open an issue with the refusal line. The construct is usually a day's work once it is named.
