Understanding Generic Constraints On Odin Drawers

To fully utilize Odin's drawer system it is necessary to understand how to use generic constraints on your custom Odin drawers.

When inheriting from an Odin drawer class, such as OdinValueDrawer, you can use C#'s generic constraints to specify which type and values you want the drawer to draw.

This following example will draw for any properties that are classes with public parameterless constructors. Odin will create an instance for the drawer for any type that matches the 'class, new()' constraint.

public class ClassDrawer<T> : OdinValueDrawer<T> where T : class, new()
{
     ...
}

You can also specify a concrete type:

public class Item { ... }

public class ItemDrawer : OdinValueDrawer<Item>
{
    ...
}

However, Odin will only use the ItemDrawer for properties of the type Item; any class that inherits from Item will not use the ItemDrawer. You can change this by making the ItemDrawer generic:

public class GenericItemDrawer<T> : OdinValueDrawer<T> where T : Item
{
    ...
}

Odin will then create a concretely typed generic instance for any Item or Item-derived type property. The same rule also applies to abstract classes and interfaces. Odin will also raise an error in the console if you try to do this:

public abstract class Weapon : Item { ... }

public class InvalidWeaponDrawer : OdinValueDrawer<Weapon>
{
    ...
}

You'll have to specify the Weapon as a generic constraint.

public class CorrectWeaponDrawer<T> : OdinValueDrawer<T> where T : Weapon
{
    ...
}

And, as mentioned above, the same rule also applies to interfaces.

public interface IMyInterface { ... }

public class InvalidMyInterfaceDrawer : OdinValueDrawer<IMyInterface> // Invalid drawer declaration
{
   ...
}

public class CorrectMyInterfaceDrawer<T> : OdinValueDrawer<T> // Valid drawer declaration
    where T : IMyInterface
{
    ...
}

For lists and generic classes, you can make drawers with multiple generic constraints.

public class ItemListDrawer<TList, TElement> : OdinValueDrawer<TList>
    where TList : IList<TElement> // Using IList lets the CustomListDrawer work for both List and arrays.
    where TElement : Item

Finally, the same rules also apply to the generic constraints of the Drawer class.

public abstract class Reference<TValue, TVariable> { ... }
public abstract class Variable<TValue> : ScriptableObject { ... }

public class InvalidReferenceDrawer<TReference, TValue> : OdinValueDrawer<TReference>
    where TReference : Reference<TValue, Variable<TValue>>
{
    ...
}

public class CorrectReferenceDrawer<TReference, TVariable, TValue> : OdinValueDrawer<TReference>
    where TReference: Reference<TValue, TVariable>
    where TVariable : Variable<TValue>
{
    ...
}

Sometimes generic constraints are not enough. Every drawer class, for this reason, has optional CanDraw methods that you override to insert additional conditional logic for where to apply the drawer.

public class AllWeaponsExceptSwordDrawer<T> : OdinValueDrawer<T>
    where T: Weapon
{
    public override bool CanDrawTypeFilter(Type type)
    {
        return type != typeof(Sword);
    }
}

It is however always best to specify as many constraints as possible to narrow down the range of types that CanDraw is queried for, since constraining the drawer through the generic constraint system is much faster than having to query CanDraw constantly for all types.