dotnet/wpf · error · InvalidOperationException

ISF decoder cannot operate on non-empty ink container

Error message

ISF decoder cannot operate on non-empty ink container

What it means

DecodeRawISF requires the target ink container to be empty before decoding; if _coreStrokes already contains strokes or extended properties, it throws InvalidOperationException. The ISF decoder overwrites the container and refuses to merge into existing ink, so reusing a populated StrokeCollection's internal container is unsupported.

Solutions

  1. Create a fresh StrokeCollection/InkSerializer for each decode operation
  2. Clear extended properties and strokes (or use a new instance) before decoding
  3. Decode into a temporary collection and merge afterwards if combining ink is needed

Example fix

// before
serializer.DecodeISF(stream1);
serializer.DecodeISF(stream2); // throws
// after
var s1 = new StrokeCollectionSerializer(); s1.DecodeISF(stream1);
var s2 = new StrokeCollectionSerializer(); s2.DecodeISF(stream2);
var combined = new StrokeCollection(s1.Strokes.Concat(s2.Strokes));
Defensive patterns

Strategy: type-guard

Validate before calling

// only decode into fresh containers
bool CanDecode(StrokeCollection c) => c.Count == 0 && c.ExtendedProperties.Count == 0;

Type guard

bool IsEmptyInkContainer(StrokeCollection c) => c.Count == 0 && c.ExtendedProperties.Count == 0;

Try / catch

try { serializer.DecodeISF(stream); }
catch (InvalidOperationException) { serializer = new StrokeCollectionSerializer(); serializer.DecodeISF(stream); }

Prevention

When it happens

Trigger: Calling DecodeISF (directly or via StrokeCollectionSerializer) on an InkSerializer whose stroke collection already holds strokes or extended properties — e.g. decoding into a loaded collection a second time.

Common situations: Reusing a single StrokeCollection/serializer instance to load multiple ink documents, calling DecodeISF twice on the same object, appending persisted ink to an already-populated collection.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Ink/InkSerializedFormat/InkSerializer.cs:324

            uint metricDescriptorTableIndex = 0;
            uint oldMetricDescriptorTableIndex = 0xFFFFFFFF;
            uint transformTableIndex = 0;
            uint oldTransformTableIndex = 0xFFFFFFFF;
            GuidList guidList = new GuidList();
            int strokeIndex = 0;

            StylusPointDescription currentStylusPointDescription = null;
            Matrix currentTabletToInkTransform = Matrix.Identity;

            _strokeDescriptorTable = new System.Collections.Generic.List<StrokeDescriptor>();
            _drawingAttributesTable = new System.Collections.Generic.List<DrawingAttributes>();
            _transformTable = new System.Collections.Generic.List<TransformDescriptor>();
            _metricTable = new System.Collections.Generic.List<MetricBlock>();

            // First make sure this ink is empty
            if (0 != _coreStrokes.Count || _coreStrokes.ExtendedProperties.Count != 0)
            {
                throw new InvalidOperationException(ISFDebugMessage("ISF decoder cannot operate on non-empty ink container"));
            }
#if OLD_ISF
            //
            // store a compressor reference at this scope, if it is needed (if there is a compresson header) and
            // therefore instanced during this routine, we will dispose of it
            // in the finally block
            //
            Compressor compressor = null;

            try
            {
#endif

            // First read the isfTag
            uint uiTag;
            uint localBytesDecoded = SerializationHelper.Decode(inputStream, out uiTag);
            if (0x00 != uiTag)
                throw new ArgumentException(SR.InvalidStream);

View on GitHub (pinned to 81131a70a4)