WHIZ161: Type Name Dissected By Hand¶
Verified by tests
TypeNameHandlingAnalyzerTests — library CI run #37346231411 (2026-10-05)
Severity: Warning Category: Type Naming
Description¶
{verified: TypeNameHandlingAnalyzerTests.Dissection_SplitOnTheAssemblyComma_ReportsWhiz161Async}
A persisted type name is not a flat string. It may carry version, culture, and public-key decoration
(Version=, Culture=, PublicKeyToken=), nested-type + separators, and generic arity (`1
with the argument list in [[...]], where more commas appear). Splitting or trimming it locally
reproduces one of those cases and misses the rest, and the miss is silent: the code keeps returning a
string, just not the key the other side wrote.
WHIZ161 reports a string dissector applied to a type-name value (see WHIZ160 for what counts as one) when the argument is a separator the helpers own:
| Method | Reported when |
|---|---|
Split |
the separator is ",", ", ", or "+" |
IndexOf, LastIndexOf |
the argument is "+", ",", or the generic arity backtick |
Replace |
the text to replace is "global::" |
Substring |
always; a substring of a type name is a hand parse whatever the offsets |
The receiver must be a type-name value. A Split(',') on a CSV field is not the rule's business.
Generated code is not analyzed, and the rule is heuristic by nature, so it ships as a warning.
Diagnostic Message¶
'{0}' is applied to a type name. Parse it with TypeNameFormatter.Parse / GetSimpleName /
GetNamespace (runtime) or TypeNameUtilities (generators) instead; hand parsing misses version
decoration, nested '+' and generic arity.
{0} is the method that was applied: Split, Substring, IndexOf, LastIndexOf, or Replace.
Common Causes¶
- Stripping the assembly from a wire name:
eventType.Split(',')[0]. The wire form of a generic type carries commas inside its[[...]]argument list, so the first segment is not the CLR form. - Extracting a nested type's simple name:
clrTypeName.Substring(clrTypeName.IndexOf('+') + 1). - Turning a generated-source name into a key:
fullyQualifiedTypeName.Replace("global::", ""). The fully qualified form renders nested types with., so the result is not the CLR key even after the prefix is gone.
How to Fix¶
Parse through the helper for that side. At runtime TypeNameFormatter.GetFullName returns the bare CLR
form of a wire name, GetSimpleName and GetNamespace take a name apart, Parse gives the parts at
once, EventTypeMatchingHelper.NormalizeTypeName strips version decoration, and
EnvelopeTypeNameHelper.ExtractInnerTypeName unwraps an envelope type name. In a generator, render the
form you need from the symbol (TypeNameUtilities.BuildClrTypeName, FormatTypeNameForRuntime,
GetSimpleName) instead of post-processing a display string.
Before (reported):
// WHIZ161: Split on the assembly comma; a generic wire name has commas inside [[...]]
public string Bare(string eventType) => eventType.Split(',')[0];
// WHIZ161: Substring and IndexOf('+') on a CLR name
public string Nested(string clrTypeName) => clrTypeName.Substring(clrTypeName.IndexOf('+') + 1);
After:
// Runtime: the bare CLR form of a wire name, and the simple name of a nested type
public string Bare(string eventType) => TypeNameFormatter.GetFullName(eventType);
public string Nested(string clrTypeName) => TypeNameFormatter.GetSimpleName(clrTypeName);
// Generator: render the key from the symbol instead of post-processing a display string
var clrTypeName = TypeNameUtilities.BuildClrTypeName(symbol);
When It Is Intentional¶
- The helpers are exempt. Code inside a type named
TypeNameFormatter,TypeNameUtilities,EventTypeMatchingHelper,EnvelopeTypeNameHelper, orTypeFormatteris not analyzed. That is where the parsing lives. {verified: TypeNameHandlingAnalyzerTests.InsideTheHelpersThemselves_EverythingIsAllowedAsync} - The receiver is not a type name. A
SplitorSubstringon a value whose name carries no key marker and does not come fromtypeof,GetType(), or a helper is not reported. - A display-only site that trims a name for a log line or a trace tag may suppress the rule for that
expression with a one-line reason (
#pragma warning disable WHIZ161 // display only: ...). - Test fixtures. The framework's own test projects turn the four rules off in
tests/.editorconfig, because fixtures compose and dissect names deliberately, including malformed ones that prove the parsers' tolerance. A consumer's test project can do the same.
Related¶
- WHIZ160: Type Name Composed By Hand
- WHIZ162: Type Names Compared Without The Matching Helper
- WHIZ163: Type-Name Key Assigned From A Hand-Built String
- Type Formatting
ai-docs/type-naming.mdin the library repository: the forms, their helpers, and the columns each form belongs to