Unity-Technologies/UnityCsReference · error · InvalidOperationException

bindings and curves must be of equal length

Error message

bindings and curves must be of equal length

What it means

Thrown by AnimationUtility.SetEditorCurves when bindings.Length != curves.Length. The batch API iterates both in lockstep and calls SetEditorCurveNoSync per index; a mismatch would desync or index out of range, so it throws InvalidOperationException before the loop.

Source

Thrown at Editor/Mono/Animation/AnimationUtility.bindings.cs:284

        [NativeMethod(ThrowsException = true)]
        extern private static void Internal_SetObjectReferenceCurve([NotNull] AnimationClip clip, EditorCurveBinding binding, ObjectReferenceKeyframe[] keyframes, bool updateMuscleClip);

        extern public static AnimationCurve GetEditorCurve([NotNull] AnimationClip clip, EditorCurveBinding binding);

        public static void SetEditorCurve(AnimationClip clip, EditorCurveBinding binding, AnimationCurve curve)
        {
            Internal_SetEditorCurve(clip, binding, curve, true);
            Internal_InvokeOnCurveWasModified(clip, binding, curve != null ? CurveModifiedType.CurveModified : CurveModifiedType.CurveDeleted);
        }

        public static void SetEditorCurves(AnimationClip clip, EditorCurveBinding[] bindings, AnimationCurve[] curves)
        {
            if (bindings == null)
                throw new ArgumentNullException(nameof(bindings), $"{nameof(bindings)} must be non-null");
            if (curves == null)
                throw new ArgumentNullException(nameof(curves), $"{nameof(curves)} must be non-null");
            if (bindings.Length != curves.Length)
                throw new InvalidOperationException($"{nameof(bindings)} and {nameof(curves)} must be of equal length");

            int length = bindings.Length;
            for (int i = 0; i < length; i++)
            {
                SetEditorCurveNoSync(clip, bindings[i], curves[i]);
            }
            SyncEditorCurves(clip);

            Internal_InvokeOnCurveWasModified(clip, new EditorCurveBinding(), AnimationUtility.CurveModifiedType.ClipModified);
        }

        internal static void SetEditorCurveNoSync(AnimationClip clip, EditorCurveBinding binding, AnimationCurve curve)
        {
            Internal_SetEditorCurve(clip, binding, curve, false);
            Internal_InvokeOnCurveWasModified(clip, binding, curve != null ? CurveModifiedType.CurveModified : CurveModifiedType.CurveDeleted);
        }

        [NativeMethod(ThrowsException = true)]

View on GitHub (pinned to 225b0fbdb5)

Solutions

  1. Assert bindings.Length == curves.Length with a context message at build time.
  2. Construct both arrays in a single pass so they stay paired.
  3. Run a length-equality check helper before every Set*Curves call.
  4. If they legitimately differ, zip to the shorter and log the dropped entries.

Example fix

// before
AnimationUtility.SetEditorCurves(clip, bindings, curves);

// after
if (bindings.Length != curves.Length)
    throw new InvalidOperationException($"bindings({bindings.Length}) != curves({curves.Length}) on {clip.name}");
AnimationUtility.SetEditorCurves(clip, bindings, curves);
Defensive patterns

Strategy: validation

Validate before calling

if (bindings.Length != curves.Length) throw new InvalidOperationException($"bindings({bindings.Length}) != curves({curves.Length})");

Prevention

When it happens

Trigger: Bindings built per-property and curves built per-clip, or vice versa. Filtering one array after building the other. Appending a curve for a new property but forgetting its binding.

Common situations: Rig migration where binding count changes. Editor tools that filter curves after constructing bindings. Copy-paste loops that increment one list conditionally.

Related errors


AI-assisted analysis of Unity-Technologies/UnityCsReference@225b0fbdb5 (2026-08-13). Data as JSON: /api/errors/73ccadbc7961484d. Report an issue: GitHub.