egametang/ET · error · Exception

不能把{value.GetType()}转换为{typeof (T)}

Error message

不能把{value.GetType()}转换为{typeof (T)}

What it means

Thrown inside the catch of BTEnv.TryGetStruct<T> when the dict value stored under the key cannot be cast to IValue<T>. Structs are boxed in ValueTypeWrap<T> (an IValue<T>) at Add time; a mismatch between the T used at AddStruct and the T used at TryGetStruct produces an InvalidCastException. Note this is a 'Try' method that returns false only for null-key and missing-key — it THROWS on type mismatch, which is a common surprise.

Source

Thrown at Packages/cn.etetet.behaviortree/Scripts/Model/Share/BTEnv.cs:115

            if (key == null)
            {
                return false;
            }
            
            if (!this.dict.TryGetValue(key, out object value))
            {
                return false;
            }

            try
            {
                IValue<T> iValue = (IValue<T>) value;
                t = iValue.Value;
                return true;
            }
            catch (InvalidCastException e)
            {
                throw new Exception($"不能把{value.GetType()}转换为{typeof (T)}", e);
            }
        }

        public T GetStruct<T>(string key) where T : struct
        {
            if (!this.dict.TryGetValue(key, out object value))
            {
                throw new Exception($"btenv not found key: {key} {typeof(T).FullName}");
            }

            try
            {
                IValue<T> iValue = (IValue<T>) value;
                return iValue.Value;
            }
            catch (InvalidCastException e)
            {
                throw new Exception($"不能把{value.GetType()}转换为{typeof (T)}", e);

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Make the generic type argument T identical at AddStruct and TryGetStruct for the same key.
  2. Audit all call sites for that key and align the types.
  3. Because TryGetStruct throws on mismatch (does not return false), wrap it in try/catch or pre-check with ContainKey plus a known-type contract.
  4. Introduce a shared constant/helper that pins the type per key to prevent drift.

Example fix

// before
env.AddStruct<int>("hp", 100);
bool ok = env.TryGetStruct<float>("hp", out float hp); // throws [41]

// after
env.AddStruct<int>("hp", 100);
bool ok = env.TryGetStruct<int>("hp", out int hp);
Defensive patterns

Strategy: type-guard

Validate before calling

// No public API exposes the stored struct type; enforce a single T per key via a shared accessor.
// Before reading, ensure AddStruct used the identical T for this key.

Type guard

// ValueTypeWrap<T> is internal-boxed; callers cannot inspect it directly.
// Discipline: pair every AddStruct<T>(k) with TryGetStruct<T>(k) using the same T.
static void AddHp(BTEnv env, int v) => env.AddStruct("hp", v);
static bool TryGetHp(BTEnv env, out int v) => env.TryGetStruct("hp", out v);

Try / catch

try { env.TryGetStruct<int>("hp", out int hp); }
catch (Exception e) when (e.Message.Contains("转换为")) { /* type mismatch for key */ }

Prevention

When it happens

Trigger: Calling AddStruct<int>(key, 100) then TryGetStruct<float>(key, out float v), or storing with one numeric type and reading with another (int vs long, int vs float). The stored ValueTypeWrap<Int32> is not assignable to IValue<Single>.

Common situations: Numeric type drift between the node that writes the struct (e.g. writing an int HP) and the node that reads it (expecting a long or float); refactor changed the generic type at one call site but not the other; copy-paste of node code with a different generic argument.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/8597482dfe0f897c. Report an issue: GitHub.