dotnet/wpf · error · IOException

SR.CanNotDeleteNonEmptyStorage

Error message

SR.CanNotDeleteNonEmptyStorage

What it means

Thrown by StorageInfo.Delete when the storage still contains child storages or streams and recursive=false. The compound file refuses to destroy a non-empty element unless the caller explicitly asks for recursive deletion, preventing accidental data loss inside the package.

Solutions

  1. Pass recursive: true to Delete when intentional full removal is wanted
  2. Check StorageIsEmpty() first and decide what to do with children
  3. Enumerate and delete children (streams and sub-storages) explicitly before calling Delete
  4. Ensure prior write steps actually consumed/emptied the storage before cleanup

Example fix

// before
storage.Delete();
// after
storage.Delete(recursive: true); // or check storage.StorageIsEmpty() first
Defensive patterns

Strategy: validation

Validate before calling

if (!storage.StorageIsEmpty() && !recursive)
    throw new InvalidOperationException("Refusing to delete non-empty storage without recursive");

Try / catch

try { storage.Delete(recursive); }
catch (IOException ex) { /* storage non-empty: enumerate children or request recursive */ }

Prevention

When it happens

Trigger: Calling Delete() (non-recursive) on a storage that has children; deleting a '\u0006DataSpaces' or TransformInfo storage that still contains streams.

Common situations: Cleanup code assuming storages are empty; incremental package writers removing intermediate storages that were reused and repopulated; deleting a page/fragment storage with content still inside.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/e6bd1d37e2484f9f. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/WindowsBase/System/IO/Packaging/CompoundFile/StorageInfo.cs:701

    /// </summary>
    /// <param name="recursive">Whether to recursive delete all existing content</param>
    /// <param name="name">Name of storage</param>
    internal bool Delete( bool recursive , string name)
    {
        bool storageDeleted = false;
        CheckDisposedStatus();
        if( null == parentStorage )
        {
            // We are the root storage, you can't "delete" the root storage!
            throw new InvalidOperationException(
                SR.CanNotDeleteRoot);
        }

        if( InternalExists(name) )
        {
            if( !recursive && !StorageIsEmpty())
            {
                throw new IOException(
                    SR.CanNotDeleteNonEmptyStorage);
            }

            InvalidateEnumerators();
            // Go ahead and delete "this" storage
            parentStorage.DestroyElement( name );
            storageDeleted = true;
        }
        //We will not throw exceptions if the storage does not exist. This is to be consistent with Package.DeletePart.
        
        return storageDeleted;
    }

    /// <summary>
    /// When a substorage is getting deleted, its references in the dataspacemanager's transform definition are removed.
    /// This is called recursively because the DeleteSubStorage deletes all its children by default.
    /// </summary>
    internal void RemoveSubStorageEntryFromDataSpaceMap(StorageInfo storageInfo)

View on GitHub (pinned to 81131a70a4)