MatchStrictness: Fuzzy Type Matching Control¶
Verified by tests
MatchStrictnessTests, TypeMatcherTests — library CI run #31657041675 (2026-08-13)
MatchStrictness is a flag enum that controls how type names are compared during fuzzy matching. It enables flexible type matching by allowing you to ignore case, version, assembly, or namespace components.
Overview¶
MatchStrictness provides: - ✅ Flag-based control over type matching behavior - ✅ Combinable flags for precise matching rules - ✅ Composite presets for common scenarios - ✅ Works with both formatted and raw type strings - ✅ Used by message association queries and type matching APIs
Quick Start¶
Basic Fuzzy Matching¶
Basic Fuzzy Matching
using Whizbang.Core;
var fullType = "ECommerce.Contracts.Events.ProductCreatedEvent, ECommerce.Contracts, Version=1.0.0";
var simpleType = "ProductCreatedEvent";
// Exact match (default)
bool exactMatch = TypeMatcher.Matches(fullType, simpleType, MatchStrictness.Exact);
// Result: false (types don't match exactly)
// Simple name match (ignore namespace, assembly, version)
bool simpleMatch = TypeMatcher.Matches(fullType, simpleType, MatchStrictness.SimpleName);
// Result: true (both have "ProductCreatedEvent")
// Case-insensitive match
bool caseMatch = TypeMatcher.Matches(
"ProductCreatedEvent",
"productcreatedevent",
MatchStrictness.CaseInsensitive
);
// Result: true (case ignored)
Combining Multiple Flags¶
Combining Multiple Flags
// Ignore both case AND version
var strictness = MatchStrictness.IgnoreCase | MatchStrictness.IgnoreVersion;
bool match = TypeMatcher.Matches(
"MyApp.Events.OrderCreated, MyApp, Version=1.0.0",
"myapp.events.ordercreated, myapp, version=2.0.0",
strictness
);
// Result: true (version and case ignored)
// Ignore namespace and case
var flexibleMatch = MatchStrictness.IgnoreNamespace | MatchStrictness.IgnoreCase;
bool matches = TypeMatcher.Matches(
"ECommerce.Events.ProductCreated",
"productcreated",
flexibleMatch
);
// Result: true (namespace and case ignored)
Individual Flags¶
Flag Definitions¶
Each flag controls a specific transformation applied to type strings before comparison:
| Flag | Value | Description |
|---|---|---|
None |
0 | Exact match (no transformations) |
IgnoreCase |
1 | Case-insensitive comparison |
IgnoreVersion |
2 | Strip version, culture, and public key token |
IgnoreAssembly |
4 | Remove assembly name |
IgnoreNamespace |
8 | Extract simple type name only (strips any assembly suffix first, so it effectively implies IgnoreAssembly) |
Transformation Order (applied sequentially): 1. IgnoreVersion → Strip version/culture/token 2. IgnoreAssembly → Remove assembly name 3. IgnoreNamespace → Extract simple type name 4. IgnoreCase → Case-insensitive comparison
IgnoreCase¶
When: Case-insensitive type name matching
IgnoreCase
var type1 = "ECommerce.Contracts.Events.ProductCreatedEvent";
var type2 = "ecommerce.contracts.events.productcreatedevent";
bool match = TypeMatcher.Matches(type1, type2, MatchStrictness.IgnoreCase);
// Result: true
// Combine with other flags
var strictness = MatchStrictness.IgnoreCase | MatchStrictness.IgnoreNamespace;
bool simpleMatch = TypeMatcher.Matches(
"MyApp.Events.OrderCreated",
"ordercreated",
strictness
);
// Result: true
IgnoreVersion¶
When: Matching types across different assembly versions
IgnoreVersion
var v1 = "MyApp.Events.OrderCreated, MyApp, Version=1.0.0, Culture=neutral";
var v2 = "MyApp.Events.OrderCreated, MyApp, Version=2.0.0, Culture=neutral";
bool match = TypeMatcher.Matches(v1, v2, MatchStrictness.IgnoreVersion);
// Result: true (version, culture stripped before comparison)
// Original types remain unchanged
// After IgnoreVersion transformation:
// v1 → "MyApp.Events.OrderCreated, MyApp"
// v2 → "MyApp.Events.OrderCreated, MyApp"
IgnoreAssembly¶
When: Matching types by namespace.TypeName only
IgnoreAssembly
var type1 = "ECommerce.Events.ProductCreated, ECommerce.Contracts";
var type2 = "ECommerce.Events.ProductCreated, ECommerce.Core";
bool match = TypeMatcher.Matches(type1, type2, MatchStrictness.IgnoreAssembly);
// Result: true (assembly name removed before comparison)
// After IgnoreAssembly transformation:
// type1 → "ECommerce.Events.ProductCreated"
// type2 → "ECommerce.Events.ProductCreated"
IgnoreNamespace¶
When: Matching types by simple name only
IgnoreNamespace
var type1 = "ECommerce.Contracts.Events.ProductCreatedEvent";
var type2 = "MyApp.Domain.Events.ProductCreatedEvent";
var type3 = "ProductCreatedEvent";
bool match1 = TypeMatcher.Matches(type1, type2, MatchStrictness.IgnoreNamespace);
// Result: true
bool match2 = TypeMatcher.Matches(type1, type3, MatchStrictness.IgnoreNamespace);
// Result: true
// After IgnoreNamespace transformation:
// type1 → "ProductCreatedEvent"
// type2 → "ProductCreatedEvent"
// type3 → "ProductCreatedEvent"
Composite Presets¶
Pre-Defined Combinations¶
| Preset | Flags | Use Case |
|---|---|---|
Exact |
None |
Exact string match (default) |
CaseInsensitive |
IgnoreCase |
Case-insensitive match |
WithoutVersionInfo |
IgnoreVersion |
Match across versions |
WithoutAssembly |
IgnoreAssembly \| IgnoreVersion |
Match namespace.Type |
SimpleName |
IgnoreNamespace \| IgnoreAssembly \| IgnoreVersion |
Match simple type name |
SimpleNameCaseInsensitive |
SimpleName \| IgnoreCase |
Match simple name, any case |
Preset Examples¶
Preset Examples
// Exact preset (default)
bool exact = TypeMatcher.Matches(
"MyApp.OrderCreated",
"myapp.ordercreated",
MatchStrictness.Exact
);
// Result: false (case matters)
// CaseInsensitive preset
bool caseInsensitive = TypeMatcher.Matches(
"MyApp.OrderCreated",
"myapp.ordercreated",
MatchStrictness.CaseInsensitive
);
// Result: true
// WithoutVersionInfo preset
bool withoutVersion = TypeMatcher.Matches(
"MyApp.Order, MyApp, Version=1.0.0",
"MyApp.Order, MyApp, Version=2.0.0",
MatchStrictness.WithoutVersionInfo
);
// Result: true
// SimpleName preset
bool simpleName = TypeMatcher.Matches(
"ECommerce.Contracts.Events.ProductCreatedEvent, ECommerce.Contracts",
"ProductCreatedEvent",
MatchStrictness.SimpleName
);
// Result: true
// SimpleNameCaseInsensitive preset
bool simpleNameCI = TypeMatcher.Matches(
"MyApp.Events.OrderCreated",
"ordercreated",
MatchStrictness.SimpleNameCaseInsensitive
);
// Result: true
Flag Combination Semantics¶
Additive Behavior¶
Flags are combined using bitwise OR, and each flag adds a transformation:
Additive Behavior
// Single flag
var ignoreCase = MatchStrictness.IgnoreCase;
// Two flags
var ignoreVersionAndCase = MatchStrictness.IgnoreVersion | MatchStrictness.IgnoreCase;
// Three flags
var flexible = MatchStrictness.IgnoreVersion | MatchStrictness.IgnoreAssembly | MatchStrictness.IgnoreCase;
// Equivalent to SimpleName + IgnoreCase
var veryFlexible = MatchStrictness.SimpleName | MatchStrictness.IgnoreCase;
// Same as: SimpleNameCaseInsensitive
Order of Operations¶
Transformations are applied in a specific order before comparison:
Order of Operations
// Example input
var type = "MyApp.Events.OrderCreated, MyApp, Version=1.0.0";
// Step 1: IgnoreVersion (if flag set)
// → "MyApp.Events.OrderCreated, MyApp"
// Step 2: IgnoreAssembly (if flag set)
// → "MyApp.Events.OrderCreated"
// Step 3: IgnoreNamespace (if flag set)
// → "OrderCreated"
// Step 4: IgnoreCase (if flag set)
// → Compare using StringComparison.OrdinalIgnoreCase
Common Scenarios¶
Scenario 1: Cross-Version Type Matching¶
When: Matching events from different assembly versions
Scenario 1: Cross-Version Type Matching
public bool IsProductEvent(string eventTypeName) {
var productEventTypes = new[] {
"ECommerce.Contracts.Events.ProductCreatedEvent, ECommerce.Contracts, Version=1.0.0",
"ECommerce.Contracts.Events.ProductUpdatedEvent, ECommerce.Contracts, Version=1.0.0",
"ECommerce.Contracts.Events.ProductDeletedEvent, ECommerce.Contracts, Version=1.0.0"
};
// Match without version - works across all versions
return productEventTypes.Any(knownType =>
TypeMatcher.Matches(
eventTypeName,
knownType,
MatchStrictness.IgnoreVersion
)
);
}
Scenario 2: User Input Matching¶
When: Matching user-provided type names (case-insensitive)
Scenario 2: User Input Matching
public IEnumerable<string> FindPerspectives(string eventName) {
// User types "ordercreated" - find "OrderCreated", "orderCreated", etc.
return PerspectiveRegistrationExtensions.GetPerspectivesForEvent(
eventName,
serviceName,
MatchStrictness.SimpleNameCaseInsensitive
);
}
Scenario 3: Plugin System Type Discovery¶
When: Discovering types from external assemblies
Scenario 3: Plugin System Type Discovery
public IEnumerable<Type> FindEventHandlers(string eventTypeName) {
var allTypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes());
// Match by simple name, ignore assembly and namespace
return allTypes.Where(t => {
var typeName = TypeFormatter.FormatType(t, TypeQualifications.Simple);
return TypeMatcher.Matches(
typeName,
eventTypeName,
MatchStrictness.SimpleName
);
});
}
Scenario 4: Configuration-Based Routing¶
When: Routing messages based on configuration
Scenario 4: Configuration-Based Routing
public class MessageRouteConfig {
public string EventPattern { get; set; } = null!;
public MatchStrictness Strictness { get; set; }
public string HandlerName { get; set; } = null!;
}
public string? FindHandler(string eventType, List<MessageRouteConfig> routes) {
foreach (var route in routes) {
if (TypeMatcher.Matches(eventType, route.EventPattern, route.Strictness)) {
return route.HandlerName;
}
}
return null;
}
// Configuration:
// - EventPattern: "ProductCreatedEvent"
// Strictness: SimpleName
// HandlerName: "ProductHandler"
//
// Matches:
// - "ECommerce.Events.ProductCreatedEvent"
// - "MyApp.Domain.ProductCreatedEvent"
// - "ProductCreatedEvent"
Flag Enum Mechanics¶
Checking Flags¶
Checking Flags
var strictness = MatchStrictness.IgnoreCase | MatchStrictness.IgnoreVersion;
// Check if a flag is set
bool hasIgnoreCase = strictness.HasFlag(MatchStrictness.IgnoreCase);
// Result: true
bool hasIgnoreNamespace = strictness.HasFlag(MatchStrictness.IgnoreNamespace);
// Result: false
// Bitwise check (equivalent)
bool hasIgnoreCaseAlt = (strictness & MatchStrictness.IgnoreCase) == MatchStrictness.IgnoreCase;
// Result: true
Adding and Removing Flags¶
Adding and Removing Flags
var strictness = MatchStrictness.IgnoreVersion;
// Add a flag
strictness |= MatchStrictness.IgnoreCase;
// Now: IgnoreVersion | IgnoreCase
// Remove a flag
strictness &= ~MatchStrictness.IgnoreVersion;
// Now: IgnoreCase
// Toggle a flag
strictness ^= MatchStrictness.IgnoreAssembly;
// Adds IgnoreAssembly if not present, removes if present
Building Strictness Dynamically¶
Building Strictness Dynamically
public MatchStrictness BuildStrictness(
bool ignoreCase,
bool ignoreVersion,
bool ignoreAssembly) {
var result = MatchStrictness.None;
if (ignoreCase) {
result |= MatchStrictness.IgnoreCase;
}
if (ignoreVersion) {
result |= MatchStrictness.IgnoreVersion;
}
if (ignoreAssembly) {
result |= MatchStrictness.IgnoreAssembly;
}
return result;
}
// Usage
var strictness = BuildStrictness(
ignoreCase: true,
ignoreVersion: true,
ignoreAssembly: false
);
// Result: IgnoreCase | IgnoreVersion
Integration with Message Associations¶
MatchStrictness is extensively used in message association queries:
Integration with Message Associations
// Find perspectives for event (exact)
var exactPerspectives = PerspectiveRegistrationExtensions.GetPerspectivesForEvent(
"ECommerce.Contracts.Events.ProductCreatedEvent",
serviceName,
MatchStrictness.Exact
);
// Find perspectives for event (simple name)
var simplePerspectives = PerspectiveRegistrationExtensions.GetPerspectivesForEvent(
"ProductCreatedEvent",
serviceName,
MatchStrictness.SimpleName
);
// Find perspectives for event (case-insensitive simple name)
var flexiblePerspectives = PerspectiveRegistrationExtensions.GetPerspectivesForEvent(
"productcreatedevent",
serviceName,
MatchStrictness.SimpleNameCaseInsensitive
);
// Find events for perspective (ignore assembly)
var events = PerspectiveRegistrationExtensions.GetEventsForPerspective(
"InventoryPerspective",
serviceName,
MatchStrictness.WithoutAssembly
);
API Reference¶
Enum Definition¶
Namespace: Whizbang.Core
Enum Definition
[Flags]
public enum MatchStrictness {
None = 0, // Exact match (default)
// Individual flags (can be combined)
IgnoreCase = 1 << 0, // 1 - Case-insensitive comparison
IgnoreVersion = 1 << 1, // 2 - Ignore Version, Culture, PublicKeyToken
IgnoreAssembly = 1 << 2, // 4 - Ignore assembly name
IgnoreNamespace = 1 << 3, // 8 - Ignore namespace, keep type name only
// Composite presets (for convenience)
Exact = None,
CaseInsensitive = IgnoreCase,
WithoutVersionInfo = IgnoreVersion,
WithoutAssembly = IgnoreAssembly | IgnoreVersion, // Match namespace.Type
SimpleName = IgnoreNamespace | IgnoreAssembly | IgnoreVersion, // Just type name
SimpleNameCaseInsensitive = SimpleName | IgnoreCase
}
Usage with TypeMatcher¶
Usage with TypeMatcher
// Match two type strings with strictness
bool match = TypeMatcher.Matches(
string typeString1,
string typeString2,
MatchStrictness strictness
);
// Examples
bool exact = TypeMatcher.Matches("MyType", "MyType", MatchStrictness.Exact);
bool caseInsensitive = TypeMatcher.Matches("MyType", "mytype", MatchStrictness.CaseInsensitive);
bool simple = TypeMatcher.Matches("MyApp.MyType", "MyType", MatchStrictness.SimpleName);
Best Practices¶
- Use presets for common cases -
SimpleName,CaseInsensitive, etc. are clearer than flag combinations - Prefer SimpleNameCaseInsensitive for user input - Most forgiving for user-provided type names
- Use IgnoreVersion for production code - Avoids brittleness from version changes
- Combine flags for precise control - Use bitwise OR for exact matching needs
- Document strictness choices - Explain why a particular strictness level was chosen
- Test edge cases - Ensure matching behavior works with nested types, generics, etc.
- Consider security implications - Overly permissive matching may allow unintended type substitutions
Common Pitfalls¶
❌ Confusing Exact with None¶
❌ Confusing Exact with None
// ❌ WRONG: Assuming None means no matching
bool match = TypeMatcher.Matches(type1, type2, MatchStrictness.None);
// Actually: Exact match (None = 0 = Exact)
// ✅ CORRECT: Use Exact for clarity
bool match = TypeMatcher.Matches(type1, type2, MatchStrictness.Exact);
❌ Over-Permissive Matching¶
❌ Over-Permissive Matching
// ❌ WRONG: Too permissive for security-sensitive code
var strictness = MatchStrictness.SimpleNameCaseInsensitive;
if (TypeMatcher.Matches(userProvidedType, "AdminCommand", strictness)) {
ExecuteAdminCommand(); // Dangerous - could match many types!
}
// ✅ CORRECT: Use exact matching against a fully qualified name for security
var strictness = MatchStrictness.Exact;
if (TypeMatcher.Matches(userProvidedType, expectedFullyQualifiedType, strictness)) {
ExecuteAdminCommand();
}
❌ Forgetting Version Implications¶
❌ Forgetting Version Implications
// ❌ WRONG: Exact match breaks with version changes
bool match = TypeMatcher.Matches(
"MyType, MyAssembly, Version=1.0.0",
"MyType, MyAssembly, Version=2.0.0",
MatchStrictness.Exact
);
// Result: false (breaks when version changes)
// ✅ CORRECT: Ignore version for robustness
bool match = TypeMatcher.Matches(
"MyType, MyAssembly, Version=1.0.0",
"MyType, MyAssembly, Version=2.0.0",
MatchStrictness.IgnoreVersion
);
// Result: true
See Also¶
- TypeQualifications - Formatting types for matching
- TypeFormatter - Formatting Type objects to strings
- TypeMatcher - Type matching with MatchStrictness
- Perspectives - Message associations using fuzzy matching