dotnet/wpf · error · ArgumentNullException

SR.EmptyScToReplace

Error message

SR.EmptyScToReplace

What it means

The single-stroke overload StrokeCollection.Replace(Stroke, StrokeCollection) throws ArgumentNullException with SR.EmptyScToReplace when strokeToReplace is null. The stroke to replace must be a valid (and contained) stroke; null is rejected up front before delegating to the collection overload.

Solutions

  1. Null-check strokeToReplace before calling Replace and skip or log when null.
  2. Ensure the stroke reference is obtained from a successful lookup (e.g. hit-test or collection member) before replacing.
  3. Catch ArgumentNullException around Replace for defensive UI code.

Example fix

// before
strokes.Replace(foundStroke, new StrokeCollection { drawn });
// after
if (foundStroke != null && strokes.Contains(foundStroke))
    strokes.Replace(foundStroke, new StrokeCollection { drawn });
Defensive patterns

Strategy: validation

Validate before calling

if (strokeToReplace != null && strokes.Contains(strokeToReplace))
    strokes.Replace(strokeToReplace, replacement);

Type guard

static bool CanReplace(StrokeCollection c, Stroke s) => s != null && c.Contains(s);

Try / catch

try { strokes.Replace(strokeToReplace, replacement); }
catch (ArgumentNullException) { /* strokeToReplace was null: skip */ }

Prevention

When it happens

Trigger: Calling Replace(null, replacementStrokes); typically a lookup (FirstOrDefault, hit-test result, event-args field) returned null and its result was passed straight into Replace.

Common situations: Replacing a stroke found via hit-testing where the hit test missed; storing a stroke reference in a field that was reset; InkCanvas selection changed handlers passing null selection.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Ink/StrokeCollection.cs:448

            //add the strokes
            //bypass this.AddRange, which calls changed events
            //and call our protected List<Stroke> directly
            ( (List<Stroke>)this.Items ).AddRange(strokes);

            RaiseStrokesChanged(strokes, removedStrokes: null, index);
        }

        /// <summary>
        /// Replace
        /// </summary>
        /// <param name="strokeToReplace"></param>
        /// <param name="strokesToReplaceWith"></param>
        public void Replace(Stroke strokeToReplace, StrokeCollection strokesToReplaceWith)
        {
            if ( strokeToReplace == null )
            {
                throw new ArgumentNullException(SR.EmptyScToReplace);
            }

            StrokeCollection strokesToReplace = new StrokeCollection();
            strokesToReplace.Add(strokeToReplace);
            this.Replace(strokesToReplace, strokesToReplaceWith);
        }

        /// <summary>
        /// Replace
        /// </summary>
        /// <param name="strokesToReplace"></param>
        /// <param name="strokesToReplaceWith"></param>
        public void Replace(StrokeCollection strokesToReplace, StrokeCollection strokesToReplaceWith)
        {
            if ( strokesToReplace == null )
            {
                throw new ArgumentNullException(SR.EmptyScToReplace);
            }

View on GitHub (pinned to 81131a70a4)