WHIZ162: Type Names Compared Without The Matching Helper¶
Verified by tests
TypeNameHandlingAnalyzerTests — library CI run #37346231411 (2026-10-05)
Severity: Warning Category: Type Naming
Description¶
{verified: TypeNameHandlingAnalyzerTests.Comparison_OfTwoTypeNameStrings_ReportsWhiz162Async}
The wire form of a type name is sometimes carried with assembly version decoration (Version=,
Culture=, PublicKeyToken=) and sometimes bare. A producer on an older build may have written a
different assembly name. A nested type may appear with . from a display string. Ordinal equality between
a persisted name and a registered one therefore fails on a build change, and it fails quietly: the filter
simply matches nothing.
WHIZ162 reports two shapes:
==or!=between two string-typed expressions whose names both carry a key marker (TypeName,ClrTypeName,EventType,EnvelopeType), such asentry.EventType == eventType;string.Equals(a, b)orstring.Equals(a, b, comparison)where both arguments are type-name values (see WHIZ160 for what counts as one).
Comparing two Type instances is exact and is not reported. Comparing two helper results (for example
NormalizeTypeName(a) == NormalizeTypeName(b)) is not reported either. Generated code is not analyzed,
and the rule is heuristic by nature, so it ships as a warning.
Diagnostic Message¶
'{0}' and '{1}' are compared as plain strings. A persisted type name may be version-decorated;
normalize both sides with EventTypeMatchingHelper.NormalizeTypeName (or resolve through
TryResolveType) so the bare and decorated forms agree.
{0} and {1} are the two operands as written in source.
Common Causes¶
- Filtering stored events against a registered type:
entry.EventType == eventTypewhile walkingwh_event_storerows. - A registry lookup by name:
string.Equals(clrTypeName, otherTypeName, StringComparison.Ordinal). - Checking an envelope-type header: comparing the wire header against
typeof(T).AssemblyQualifiedName, which is the versioned form and changes with every assembly version.
How to Fix¶
Go through EventTypeMatchingHelper, which is keyed by every form a producer may have written:
EventTypeMatchingHelper.BuildTypeLookup(candidateTypes)builds the lookup once.EventTypeMatchingHelper.TryResolveType(lookup, storedName, out type)resolves a stored name, raw first and normalized second.EventTypeMatchingHelper.NormalizeTypeName(name)strips the version decoration when a bare comparison is unavoidable; apply it to both sides.
The polymorphic event-store reads and the resurrection-on-wake probe use exactly this.
Before (reported):
// WHIZ162: a stored name may be version-decorated; ordinal equality misses its bare form
public bool Same(Entry e, string eventType) => e.EventType == eventType;
// WHIZ162: string.Equals between two type-name values is the same comparison
public bool SameEquals(string clrTypeName, string otherTypeName) =>
string.Equals(clrTypeName, otherTypeName, StringComparison.Ordinal);
After:
// Resolve a stored name against the registered candidates (raw first, normalized second)
var lookup = EventTypeMatchingHelper.BuildTypeLookup(handledEventTypes);
if (EventTypeMatchingHelper.TryResolveType(lookup, storedEventType, out var resolved)) {
// resolved is the registered Type the stored name refers to
}
// When a bare comparison is unavoidable, normalize both sides
var same = EventTypeMatchingHelper.NormalizeTypeName(eventType)
== EventTypeMatchingHelper.NormalizeTypeName(storedEventType);
// Two Type instances compare exactly; the rule is about strings
var sameType = entry.EventType == eventType; // entry.EventType is a Type here
When It Is Intentional¶
- The helpers are exempt. Code inside a type named
TypeNameFormatter,TypeNameUtilities,EventTypeMatchingHelper,EnvelopeTypeNameHelper, orTypeFormatteris not analyzed. The matching helper compares strings because that is its job. {verified: TypeNameHandlingAnalyzerTests.InsideTheHelpersThemselves_EverythingIsAllowedAsync} - The values are types, not strings.
Type == Typeis exact and is never reported. - A display-only site (deduplicating names in a diagnostic dump, sorting a report) may suppress the
rule for that expression with a one-line reason (
#pragma warning disable WHIZ162 // display only: ...). - Test fixtures. The framework's own test projects turn the four rules off in
tests/.editorconfig, because fixtures compare hand-written names deliberately. A consumer's test project can do the same.
Related¶
- WHIZ160: Type Name Composed By Hand
- WHIZ161: Type Name Dissected By Hand
- 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 how a persisted name is matched against a registered one