Documentation version: 0.3.5 · All examples · Package guide
This guide shows every generic or hierarchy shape that LCGUdonSharp rejects at build time and the supported replacement. Rejected examples are self-contained illustrations with the relevant declarations included. They remain in Markdown intentionally so the package's normal example assembly continues to compile; use one case at a time when testing a diagnostic in a Unity project.
These scripts are active members of the registered UdonSharp example assembly
(LogicCuteGuy.LCGUdonSharp.Examples.asmdef, registered for UdonSharp
compilation by LogicCuteGuy.LCGUdonSharp.Examples.USharp.asset, see
Key terms).
Each runs the supported pattern and keeps the corresponding rejected syntax as
comments so Unity can still compile the package. Every script has a matching
UdonSharpProgramAsset, and Interact() runs its active demonstration.
| Restriction | UdonSharp example |
|---|---|
| Open generics | OpenGenericsExample.cs |
| Generic behaviours | GenericBehavioursExample.cs |
| Generic heap objects | GenericHeapObjectsExample.cs |
| Collections, JSON, bytes, and bits | ListTypesExample.cs |
| Unsupported interface members | InterfaceMembersExample.cs |
| Multiple concrete bases | MultipleConcreteBasesExample.cs |
An unbound type, or a runtime type that still contains a type parameter, has no single Udon representation.
Rejected
using System;
using UdonSharp;
public class Converter<T> { }
public static class GenericTools<T> { }
public class OpenGenericExamples : UdonSharpBehaviour
{
// Rejected: Open generic type 'Converter<>' is not supported by U#.
public Type GetDefinition() { return typeof(Converter<>); }
// Rejected inside a generic method: T is still open at runtime.
public Type GetRuntimeType<T>() { return typeof(GenericTools<T>); }
}Use a concrete type at each call site. Generic helpers must be specialized at build time; do not inspect or store their open runtime type.
Replacement
using UdonSharp;
public static class GenericTools<T>
{
public static T Identity(T value)
{
return value;
}
}
public interface IValueSource<T>
{
T Read();
}
public class ClosedGenericExamples : UdonSharpBehaviour
{
public int Copy(IValueSource<int> source, int value)
{
int copied = GenericTools<int>.Identity(value);
return source.Read() + copied;
}
}Unity and Udon program assets require one concrete behaviour type.
Rejected
using UdonSharp;
// Rejected even when the class has no UdonSharpProgramAsset.
public class Meter<T> : UdonSharpBehaviour { }Make the behaviour concrete and move reusable algorithms into static generic helpers.
Replacement
using UdonSharp;
public static class GenericTools<T>
{
public static T Identity(T value) { return value; }
}
public class IntMeter : UdonSharpBehaviour
{
public int value;
public int CopyValue()
{
return GenericTools<int>.Identity(value);
}
}Generic reference-type instances have no supported Udon heap layout. The rule applies to fields, locals, parameters, returns, allocations, and non-generic classes derived from a constructed generic base.
Rejected
using UdonSharp;
public class Box<T> { }
public class IntBox : Box<int> { }
public class GenericHeapExamples : UdonSharpBehaviour
{
private Box<int> box; // Rejected type reference
private IntBox inheritedGenericBox; // Rejected through generic base
public Box<int> Create() { return new Box<int>(); } // Rejected allocation
}Use arrays, parallel arrays, concrete behaviours, or SDK value types such as
VRCUrl instead.
Replacement
using UdonSharp;
using VRC.SDKBase;
public class ArrayBackedItems : UdonSharpBehaviour
{
public int[] itemIds;
public string[] itemNames;
public VRCUrl[] itemUrls;
}Static generic helpers and closed generic interfaces are not heap objects and remain valid specialization inputs.
int result = GenericTools<int>.Identity(value); // Static generic helpers
IValueSource<int> source = intSource; // Closed generic interfacesExact List<T> and Dictionary<TKey,TValue> types are compiler-lowered to
VRChat DataList and DataDictionary values. The runnable
ListTypesExample.cs demonstrates initializers,
indexers, mutation, System.Text.Json-style round trips, a Manual-mode synced
list, UTF-8 bytes, BitConverter, Buffer.BlockCopy, and bitwise operators.
In 0.3.x, editor proxy serialization uses the same lowered storage for fields,
nested collections, and arrays of collections. Null and empty values stay
distinct, and primitive or enum elements keep their exact DataToken type.
List<int> values = new List<int> { 1, 2, 3 };
Dictionary<string, int> scores = new Dictionary<string, int>
{
{ "alpha", 10 },
};
scores["count"] = values.Count;
string json = JsonSerializer.Serialize(scores);
Dictionary<string, int> copy =
JsonSerializer.Deserialize<Dictionary<string, int>>(json);The compiler only recognizes the exact BCL collection definitions. Interfaces,
derived collections, arbitrary enumerable sources, custom comparers, and user
types that merely share the names List or Dictionary are not lowered.
public class Scores : List<int> { }
IList<int> interfaceValues; // Rejected: collection interface
Scores derivedValues; // Rejected: derived collectionCollection fields cannot use Unity Inspector serialization. Keep local fields
private, or add [NonSerialized] to a public field. A synchronized collection
must use [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] and
[UdonSynced, NonSerialized]; ownership transfer and RequestSerialization()
remain explicit.
U# interfaces are build-time contracts for instance methods and ordinary instance properties. The following members are rejected:
Rejected
using System;
using UdonSharp;
public interface IInvalidContract
{
const int Version = 1; // Field or constant
event Action Changed; // Event
int this[int index] { get; } // Indexer
static int StaticValue { get { return 0; } } // Static property
static void Reset() { } // Static method
T Convert<T>(T value); // Generic method
int DefaultValue() { return 1; } // Default interface method
class NestedType { } // Nested type
}
public class InvalidContractExample : UdonSharpBehaviour, IInvalidContract
{
public event Action Changed;
public int this[int index] { get { return index; } }
public T Convert<T>(T value) { return value; }
}Use normal instance methods and properties with concrete parameter types.
Replacement
using UdonSharp;
public interface IValueSource<T>
{
T Value { get; }
T Read();
}
public class IntValueSource : UdonSharpBehaviour, IValueSource<int>
{
public int Value { get; private set; }
public int Read() { return Value; }
}A behaviour may implement multiple supported interfaces, including closed generic interfaces.
C# and Roslyn reject multiple concrete base classes before Udon lowering.
Rejected
public class FirstBase { }
public class SecondBase { }
// Rejected with CS1721.
public class Combined : FirstBase, SecondBase { }Use one concrete base plus interfaces, or use composition.
Replacement
using UdonSharp;
// HelperBehaviour.cs
public class HelperBehaviour : UdonSharpBehaviour
{
public int ReadValue() { return 0; }
public void ResetValue() { }
}using UdonSharp;
// Combined.cs
public abstract class FirstBase : UdonSharpBehaviour { }
public interface IReadable { int Read(); }
public interface IResettable { void ResetValue(); }
public class Combined : FirstBase, IReadable, IResettable
{
public HelperBehaviour helper; // Composition
public int Read()
{
return helper.ReadValue();
}
public void ResetValue()
{
helper.ResetValue();
}
}Task<T> is exempt from the generic heap-type diagnostic because async lowering
uses it as a compiler-only handle. It is not a normal heap object and cannot be
constructed directly.
The following is future-lowering pseudocode, not a runnable example yet:
Task<int> pending = GetValueAsync(); // Local handle produced by lowered async code
new Task<int>(SomeFunction); // Rejected generic heap-object constructionThis exemption preserves the async lowering contract; it does not make CLR
threads, Task.Run, arbitrary task construction, or runtime task objects
available in Udon. The snippet documents the reserved representation; it becomes
runnable only when the corresponding async lowering and producer method are
available.