WHIZ300: Inconsistent Perspective Model Types¶
Verified by tests
PerspectiveModelConsistencyAnalyzerTests — library CI run #31657041675 (2026-08-13)
Severity: Error Category: Perspective Validation
Description¶
This error is reported when a class implements multiple IPerspectiveFor<TModel, TEvent> and/or IPerspectiveWithActionsFor<TModel, TEvent> interfaces with different TModel types. All perspective interfaces on a single class must share the same model type for the perspective runner to function correctly.
The PerspectiveModelConsistencyAnalyzer catches this at compile time, preventing runtime failures in the perspective runner.
Diagnostic Message¶
Perspective '{ClassName}' implements multiple perspective interfaces with different model types: {ModelTypes}. All perspective interfaces on a class must use the same TModel type.
Common Causes¶
- Copy-paste error - Copying interface declarations from another perspective and forgetting to change the model type
- Refactoring mistake - Renaming or splitting a model type without updating all interface declarations
- Accidental mixing - Adding an interface for a different aggregate's model to the wrong perspective class
How to Fix¶
Ensure every IPerspectiveFor and IPerspectiveWithActionsFor interface on the class uses the same TModel type argument.
Before (causes WHIZ300)¶
Before (causes WHIZ300)
// WHIZ300: Different model types (OrderView vs ProductView)
public class OrderPerspective :
IPerspectiveFor<OrderView, OrderCreated>,
IPerspectiveWithActionsFor<ProductView, ProductDeleted> {
public OrderView Apply(OrderView model, OrderCreated @event) {
return model with { OrderId = @event.OrderId };
}
public ProductView Apply(ProductView model, ProductDeleted @event) {
return model with { IsDeleted = true };
}
}
Fix Option 1: Use the same model type¶
Fix Option 1: Use the same model type
public class OrderPerspective :
IPerspectiveFor<OrderView, OrderCreated>,
IPerspectiveWithActionsFor<OrderView, OrderDeleted> {
public OrderView Apply(OrderView model, OrderCreated @event) {
return model with { OrderId = @event.OrderId };
}
public OrderView Apply(OrderView model, OrderDeleted @event) {
return model with { IsDeleted = true };
}
}
Fix Option 2: Split into separate perspective classes¶
Fix Option 2: Split into separate perspectives
public class OrderPerspective :
IPerspectiveFor<OrderView, OrderCreated> {
public OrderView Apply(OrderView model, OrderCreated @event) {
return model with { OrderId = @event.OrderId };
}
}
public class ProductPerspective :
IPerspectiveWithActionsFor<ProductView, ProductDeleted> {
public ProductView Apply(ProductView model, ProductDeleted @event) {
return model with { IsDeleted = true };
}
}
Valid Usage Examples¶
Valid Usage Examples
// All interfaces use OrderView as TModel - no WHIZ300
public class OrderPerspective :
IPerspectiveFor<OrderView, OrderCreated>,
IPerspectiveFor<OrderView, OrderUpdated>,
IPerspectiveWithActionsFor<OrderView, OrderDeleted>,
IPerspectiveWithActionsFor<OrderView, OrderCancelled> {
public OrderView Apply(OrderView model, OrderCreated @event) {
return model with { OrderId = @event.OrderId, Status = "Created" };
}
public OrderView Apply(OrderView model, OrderUpdated @event) {
return model with { Status = @event.NewStatus };
}
public OrderView Apply(OrderView model, OrderDeleted @event) {
return model with { IsDeleted = true };
}
public OrderView Apply(OrderView model, OrderCancelled @event) {
return model with { Status = "Cancelled" };
}
}
Suppressing This Diagnostic¶
In rare cases where you intentionally want to suppress this error (e.g., testing analyzer behavior), use pragma suppression:
Suppressing This Diagnostic
#pragma warning disable WHIZ300 // Intentional inconsistent model types for testing
public class TestPerspective :
IPerspectiveFor<OrderView, OrderCreated>,
IPerspectiveWithActionsFor<ProductView, ProductDeleted> { }
#pragma warning restore WHIZ300
You can also suppress via .editorconfig:
Why This Matters¶
The perspective runner processes events by routing them to the correct Apply method on a perspective class. Internally, the generated runner and its storage registration resolve a single TModel per perspective class — one perspective table, one model type. When a class declares multiple perspective interfaces with different model types, the runner cannot determine which model to load and persist, and perspective processing fails at runtime.
The WHIZ300 analyzer moves this error to compile time, providing immediate feedback in your IDE.
Analyzer Details¶
| Property | Value |
|---|---|
| Diagnostic ID | WHIZ300 |
| Category | Whizbang.PerspectiveValidation |
| Default Severity | Error |
| Enabled by Default | Yes |
| Analyzer | PerspectiveModelConsistencyAnalyzer |
| ID Range | WHIZ300-399 (perspective interface validation) |
The analyzer registers on ClassDeclaration syntax nodes and inspects all implemented interfaces. It only triggers when a class implements two or more perspective interfaces (IPerspectiveFor<,> or IPerspectiveWithActionsFor<,>) and the first type argument (TModel) differs between them.
AOT Compatibility¶
The analyzer is fully AOT-compatible:
- Runs at compile time via Roslyn's
DiagnosticAnalyzerinfrastructure - Uses
INamedTypeSymbol.AllInterfacesfor type discovery (no reflection) - Concurrent execution enabled for performance
See Also¶
- Perspective Discovery - Compile-time perspective registration
- WHIZ400: Invalid Type Argument for ILensQuery - Related compile-time type validation
Version 1.0.0 - Foundation Release