Connascence in .NET: How to Reduce Coupling Without Losing Delivery Speed
Introduction
Although it is an old concept, Connascence is still not well understood by some developers. This is relevant because excessive coupling remains one of the main causes of fragility in enterprise systems. In modern platforms like .NET, the challenge is not only to build quickly, but also to enable safe evolution over time. The concept of Connascence provides a technical vocabulary to diagnose dependencies and guide refactorings with architectural criteria (PAGE-JONES, 1992).
In this article, we explore how to apply Connascence to APIs, services, and event-driven flows in .NET 10, showing practices to shift coupling from distributed areas to more local and controllable points.
Connascence Fundamentals
Definition and motivation
Connascence describes the degree of shared knowledge required between parts of a system for it to function correctly. The greater the shared knowledge and the farther it is spread across the system, the higher the risk of regression with future changes (PAGE-JONES, 1992).
Main types of Connascence
In day-to-day .NET development, the most common types appear as:
- Connascence of Name (CoN): dependency on exact names (methods, properties, JSON fields).
- Connascence of Type (CoT): dependency on specific types and formats.
- Connascence of Meaning (CoM): dependency on implicit conventions, without explicit documentation.
- Connascence of Execution/Timing (CoE/CoTg): dependency on execution order and timing.
- Connascence of Algorithm (CoA): multiple points replicating the same algorithm.
- Connascence of Position (CoP): dependency on the position of arguments or fields.
The goal is not to eliminate all connascence, but to reduce its most harmful forms and, when inevitable, keep them within local and highly testable scopes (FOWLER, 2002).
Connascence in .NET APIs
Example of position and meaning coupling
A recurring antipattern is exposing APIs with positional parameters without explicit semantics:
// Antipattern: high risk of CoP and CoM
public sealed class PricingService
{
public decimal Calculate(decimal value, decimal discount, decimal tax)
=> (value - discount) * (1 + tax);
}
// Fragile call: the wrong order compiles and fails only at runtime/logic
var total = pricing.Calculate(100m, 0.2m, 10m);
Refactoring to an explicit contract reduces ambiguities and facilitates evolution:
public sealed record class PricingInput
{
public required decimal GrossAmount { get; init; }
public required decimal DiscountAmount { get; init; }
public required decimal TaxRate { get; init; }
}
public sealed class PricingService
{
public decimal Calculate(PricingInput input)
=> (input.GrossAmount - input.DiscountAmount) * (1 + input.TaxRate);
}
var total = pricing.Calculate(new PricingInput
{
GrossAmount = 100m,
DiscountAmount = 10m,
TaxRate = 0.2m
});
The result of the connascence analysis executed in the terminal is presented below.
Versioning and name connascence
Changing field names in public contracts creates distributed CoN between providers and consumers. In .NET 10, the safest path remains additive evolution with DTOs versioned by namespace and explicit serialization, maintaining backward compatibility (NEWMAN, 2021).
namespace Contracts.V1;
public sealed record class OrderDto
{
public required Guid Id { get; init; }
public required decimal TotalAmount { get; init; }
}
namespace Contracts.V2;
public sealed record class OrderDto
{
public required Guid Id { get; init; }
public required decimal TotalAmount { get; init; }
public string? Currency { get; init; }
}
Connascence in Distributed Architectures
Events and temporal connascence
In messaging, assuming a fixed order of events between different services is a dangerous form of CoE/CoTg. Instead, prefer self-descriptive messages, idempotency, and state-driven processing rather than strict sequencing (VERNON, 2013).
public sealed record class PaymentReceivedV1
{
public required Guid OrderId { get; init; }
public required decimal Amount { get; init; }
public required DateTimeOffset OccurredAt { get; init; }
public required string EventId { get; init; }
}
public sealed class PaymentProjection
{
private readonly HashSet<string> _processed = [];
public bool TryApply(PaymentReceivedV1 evt)
{
if (!_processed.Add(evt.EventId))
{
return false; // Idempotency avoids timing dependency and repetition
}
// Update state deterministically
return true;
}
}
Canonical contracts as decoupling reducers
When multiple systems exchange data with similar semantics, a canonical model reduces CoM and CoN because it makes the ubiquitous language and transformation rules explicit at well-known points. This arrangement usually works best when combined with dedicated mappers and automated contract tests (EVANS, 2003).
Practical Strategies for .NET Teams
1. Measure connascence during technical reviews
During code review, ask: does this change add name, order, type or timing dependencies between distant modules? If so, reconsider the design before moving to production.
2. Localize strong connascence within the same module
Strong connascence is less harmful when confined to the same component, as coordinated changes occur in the same deployment cycle. This principle aligns with the recommendation to increase cohesion and reduce cross-cutting coupling (MARTIN, 2017).
3. Automate contract checks
In .NET pipelines, serialization and version compatibility tests prevent silent regression:
public sealed class ContractCompatibilityTests
{
[Fact]
public void V2_Should_Deserialize_V1_Payload()
{
var v1Json = """
{
"Id": "7a6a16de-31c0-4770-9413-6f6cefcf7a3a",
"TotalAmount": 125.50
}
""";
var dto = JsonSerializer.Deserialize<Contracts.V2.OrderDto>(v1Json);
Assert.NotNull(dto);
Assert.Equal(125.50m, dto!.TotalAmount);
Assert.Null(dto.Currency);
}
}
4. Reduce algorithm connascence with unique services
When the same rule appears in multiple places, centralize it in a single domain service to avoid distributed CoA and semantic divergence between teams.
Conclusion
Connascence offers a pragmatic lens to evolve .NET systems with less regression and more predictability. By identifying dependencies by name, type, meaning, time, and algorithm, technical teams make design decisions based on real maintenance risk, not just stylistic preference.
In corporate scenarios, combining canonical contracts, additive versioning, idempotence, and compatibility testing transforms coupling from a hidden issue into a manageable asset. The result is a more resilient architecture, capable of growing without sacrificing team autonomy or the stability of existing integrations.
References
-
PAGE-JONES, Meilir. The practical guide to structured systems design. 2nd ed. Englewood Cliffs: Yourdon Press, 1992.
-
EVANS, Eric. Domain-driven design: tackling complexity in the heart of software. Boston: Addison-Wesley, 2003.
-
FOWLER, Martin. Patterns of enterprise application architecture. Boston: Addison-Wesley, 2002.
-
NEWMAN, Sam. Building microservices: designing fine-grained systems. 2nd ed. Sebastopol: O'Reilly Media, 2021.
-
VERNON, Vaughn. Implementing domain-driven design. Boston: Addison-Wesley, 2013.
-
MARTIN, Robert C. Clean architecture: a craftsman's guide to software structure and design. Boston: Pearson, 2017.