dotnet/wpf · error · ArgumentException

PropertyOfClassCannotBeNull (CultureInfo, TextRunProperties)

Error message

PropertyOfClassCannotBeNull (CultureInfo, TextRunProperties)

What it means

FetchTextRun maps properties.CultureInfo through CultureMapper.GetSpecificCulture and throws ArgumentException(SR.PropertyOfClassCannotBeNull with CultureInfo/TextRunProperties) when the resulting specific culture is null. A concrete culture is required for shaping, measurement, and line-breaking.

Solutions

  1. Set CultureInfo to a specific culture (e.g. new CultureInfo("en-US")) on the run properties
  2. Use CultureInfo.InvariantCulture-derived specific culture or CultureInfo.CurrentUICulture if neutral
  3. Pre-validate with CultureMapper.GetSpecificCulture in your TextSource before returning the run

Example fix

// before
props.CultureInfo = new CultureInfo("en"); // neutral
// after
props.CultureInfo = new CultureInfo("en-US"); // specific
Defensive patterns

Strategy: validation

Validate before calling

var specific = CultureMapper.GetSpecificCulture(run.Properties.CultureInfo);
if (specific == null) run.Properties.CultureInfo = new CultureInfo("en-US");

Type guard

bool HasSpecificCulture(TextRunProperties p) => CultureMapper.GetSpecificCulture(p.CultureInfo) != null;

Try / catch

try { formatter.FormatLine(...); } catch (ArgumentException) { /* set a specific culture and retry */ }

Prevention

When it happens

Trigger: TextRun.Properties.CultureInfo is null or a neutral culture (e.g. "en" not "en-US") that CultureMapper cannot map to a specific culture.

Common situations: Threads with no culture set (CultureInfo.CurrentCulture null in defaults); assigning neutral cultures to runs; deserialized properties losing culture info.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/TextFormatting/TextRunCacheImp.cs:128

                if (plsrun == Plsrun.Text || plsrun == Plsrun.InlineObject)
                {
                    TextRunProperties properties = textRun.Properties;

                    if (properties == null)
                        throw new ArgumentException(SR.TextRunPropertiesCannotBeNull);

                    if (properties.FontRenderingEmSize <= 0)
                        throw new ArgumentException(SR.Format(SR.PropertyOfClassMustBeGreaterThanZero, "FontRenderingEmSize", "TextRunProperties"));

                    double realMaxFontRenderingEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm;

                    if (properties.FontRenderingEmSize > realMaxFontRenderingEmSize)
                        throw new ArgumentException(SR.Format(SR.PropertyOfClassCannotBeGreaterThan, "FontRenderingEmSize", "TextRunProperties", realMaxFontRenderingEmSize));

                    CultureInfo culture = CultureMapper.GetSpecificCulture(properties.CultureInfo);

                    if (culture == null)
                        throw new ArgumentException(SR.Format(SR.PropertyOfClassCannotBeNull, "CultureInfo", "TextRunProperties"));

                    if (properties.Typeface == null)
                        throw new ArgumentException(SR.Format(SR.PropertyOfClassCannotBeNull, "Typeface", "TextRunProperties"));
                }


                //
                // TextRun is specifial to SpanVector because TextRun also encodes position which needs to be 
                // consistent with the positions encoded by SpanVector. In run cache, the begining of a span 
                // should always correspond to the begining of a cached text run. If the end of the currently fetched 
                // run overlaps with the begining of an already cached run, the begining of the cached run needs to be 
                // adjusted as well as its span. Because we can't gurantee the correctness of the overlapped range 
                // so we'll simply remove the overlapped runs here.
                //                

                // Move the rider to the end of the current run
                textRunSpanRider.At(cpFetch + textRun.Length - 1);
                _latestPosition = textRunSpanRider.SpanPosition;

View on GitHub (pinned to 81131a70a4)