Language support

What compiles today, and the parts of C# that Udon can never run.

Updated 2026-09-08

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

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

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:

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

abstract classes abstract members virtual and override base. calls abstract behaviours

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.

Statements and expressions

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

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

component lookup Instantiate and Destroy transforms and physics Mathf and the value types Random and Time coroutines Invoke family async and await VRCPlayerApi Networking pickups and stations UdonBehaviour references other behaviours' Unity members arrays of Unity and SDK types casts between Unity object types

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.

Writing to shaders and materials

VRCShader globals material.SetFloat and friends MaterialPropertyBlock EnableKeyword

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

Something went wrong Reload