tui-cs/Terminal.Gui · error · NotSupportedException

Cloning of frozen or immutable dictionaries like {type.Name}

Error message

Cloning of frozen or immutable dictionaries like {type.Name} is not supported.

What it means

Thrown by DeepCloner.CheckForUnsupportedDictionaryTypes (DeepCloner.cs:446-465) when the type or any of its base types is a generic type whose full name starts with 'System.Collections.Frozen' or 'System.Collections.Immutable'. Frozen and immutable dictionaries have no mutation API, so the cloner (which builds a mutable copy) cannot clone them. This is an explicit NotSupportedException.

Source

Thrown at Terminal.Gui/Configuration/DeepCloner.cs:459

            // Fallback to parameterless constructor if comparer constructor is not available
            return (IDictionary)Activator.CreateInstance (dictType)!;
        }
    }

    private static void CheckForUnsupportedDictionaryTypes (Type type)
    {
        Type? currentType = type;

        while (currentType != null && currentType != typeof (object))
        {
            if (currentType.IsGenericType)
            {
                string? genericTypeName = currentType.GetGenericTypeDefinition ().FullName;

                if (genericTypeName != null
                    && (genericTypeName.StartsWith ("System.Collections.Frozen") || genericTypeName.StartsWith ("System.Collections.Immutable")))
                {
                    throw new NotSupportedException ($"Cloning of frozen or immutable dictionaries like {type.Name} is not supported.");
                }
            }

            currentType = currentType.BaseType;
        }
    }

    #endregion Dictionary Support

    #region AOT Support

    private static TScopeT CloneScope<TScopeT> (TScopeT scope, ConcurrentDictionary<object, object> visited)
        where TScopeT : Scope<TScopeT>, new()
    {
        TScopeT clonedScope = new ();
        visited.TryAdd (scope, clonedScope);

        foreach (KeyValuePair<string, ConfigProperty> kvp in scope)

View on GitHub (pinned to 2e47b11478)

Solutions

  1. Store the dictionary as a plain Dictionary<,> or ConcurrentDictionary<,> in the clonable property; call ToFrozenDictionary/ToImmutableDictionary only at read sites that do not clone.
  2. If immutability is required across the boundary, convert to Dictionary just before assigning to the ConfigProperty.
  3. For ThemeScope/SettingsScope, these are already ConcurrentDictionary and are handled by the typed path at line 251 — do not replace them with frozen variants.
  4. If a nested property is the offender, mark it as a simple/non-cloned type or wrap it so DeepCloneInternal returns it by reference (e.g. implement a custom scope clone).

Example fix

// before
public FrozenDictionary<string, Scheme> Schemes { get; set; }
    = themes.ToFrozenDictionary ();

// after — keep clonable storage mutable; freeze only at read time
public Dictionary<string, Scheme> Schemes { get; set; } = new (themes);
public FrozenDictionary<string, Scheme> SchemesFrozen => Schemes.ToFrozenDictionary ();
Defensive patterns

Strategy: validation

Validate before calling

static bool IsClonableDictionary (object? obj)
{
    if (obj is not IDictionary) return false;
    Type? t = obj.GetType ();
    while (t is not null && t != typeof (object))
    {
        if (t.IsGenericType)
        {
            string? name = t.GetGenericTypeDefinition ().FullName;
            if (name is not null
                && (name.StartsWith ("System.Collections.Frozen")
                    || name.StartsWith ("System.Collections.Immutable")))
            {
                return false;
            }
        }
        t = t.BaseType;
    }
    return true;
}

if (!IsClonableDictionary (myDict))
{
    myDict = new Dictionary<,>(myDict); // thaw before cloning
}

Type guard

static bool IsMutableDictionary (object obj)
{
    string? ns = obj.GetType ().Namespace;
    return obj is IDictionary
        && ns != "System.Collections.Frozen"
        && ns != "System.Collections.Immutable";
}

Try / catch

try
{
    var clone = DeepCloner.DeepClone (configWithDict);
}
catch (NotSupportedException ex) when (ex.Message.Contains ("frozen or immutable"))
{
    // Convert to a mutable Dictionary<,> before assigning to the ConfigProperty.
}

Prevention

When it happens

Trigger: A configuration property holds a FrozenDictionary<TKey,TValue>, ImmutableDictionary<TKey,TValue>, ImmutableSortedDictionary<,>, or any type derived from those families. CloneDictionary reaches the unsupported-type check before attempting construction and rejects it.

Common situations: A user calls ToFrozenDictionary()/ToImmutableDictionary() on a schemes/themes map for performance and stores the result in a clonable ConfigProperty. A .NET 8+ migration introduces FrozenDictionary in a shared config object. A test fixture builds an ImmutableDictionary and passes it through ConfigurationManager.Apply.

Related errors


AI-assisted analysis of tui-cs/Terminal.Gui@2e47b11478 (2026-08-13). Data as JSON: /api/errors/27e8c7bbad1a4a6f. Report an issue: GitHub.