Understanding the in Parameter Modifier in C
In C#, the in keyword is a parameter modifier used to
pass arguments by reference rather than by value, while explicitly
enforcing read-only access within the called method. This article covers
the fundamental role of the in modifier, how it differs
from ref and out, its performance benefits
when handling large value types, and key considerations like defensive
copies to avoid unintended overhead.
What Is the in
Modifier?
Introduced in C# 7.2, the in parameter modifier
instructs the compiler to pass an argument by reference while preventing
the called method from modifying its value. It combines the performance
advantage of pass-by-reference with the safety of pass-by-value
immutability.
void DisplayPoint(in GeoPoint point)
{
// point.X = 10; // Compile-time error: Cannot assign to variable because it is a readonly variable
Console.WriteLine($"Lat: {point.Latitude}, Long: {point.Longitude}");
}Primary Roles and Benefits
1. Performance Optimization for Large Structs
When value types (such as struct) are passed to methods
by default, the runtime creates a complete copy of the data on the
stack. For small primitives (like int or
bool), this copy is negligible. However, for large custom
structs, copying consumes CPU cycles and increases stack memory
usage.
Using in passes a reference (pointer) to the existing
memory location instead of copying the struct, dramatically reducing
overhead in performance-critical code paths.
2. Enforcing Immutability
Unlike the ref modifier, which allows the invoked method
to alter the original data, an in parameter is strictly
read-only. Any attempt to reassign the parameter or modify its fields
causes a compile-time error. This clearly communicates the method's
design intent: the data is strictly an input.
Key Rules and Usage Characteristics
Must Be Initialized: Unlike
outparameters, any variable passed with theinmodifier must be fully initialized before passing it to the method.Optional Call-Site Modifier: At the call site, specifying
inis usually optional. The compiler resolves the overload automatically unless ambiguity exists.GeoPoint p = new GeoPoint(45.0, 90.0); DisplayPoint(p); // Valid DisplayPoint(in p); // Also valid and explicitOverloading Restrictions: You cannot overload methods where the only difference is between
in,out, andref. However, you can overload a method where one version takes a standard by-value parameter and the other usesin.
The "Defensive Copy" Pitfall
When passing a standard mutable struct using
in, the C# compiler prioritizes immutability over
performance. If you invoke a method or property on that struct, the
compiler cannot guarantee that the member does not mutate internal
state. To safeguard immutability, the compiler creates a temporary
"defensive copy" of the struct before running the member.
This defensive copying can negate the performance benefits of passing by reference.
How to Prevent Defensive Copies
To avoid this issue, declare the struct as
readonly struct:
public readonly struct GeoPoint
{
public double Latitude { get; }
public double Longitude { get; }
public GeoPoint(double lat, double lon)
{
Latitude = lat;
Longitude = lon;
}
}When a struct is marked as readonly, the compiler
guarantees that no internal member can mutate state, completely
eliminating the need for defensive copies when accessed via
in.
Comparison: in vs.
ref vs. out
| Modifier | Passed By | Caller Must Initialize? | Method Can Modify? | Call-Site Keyword Required? |
|---|---|---|---|---|
in |
Reference | Yes | No (Read-Only) | Optional |
ref |
Reference | Yes | Yes (Read/Write) | Required |
out |
Reference | No | Yes (Must Assign) | Required |
When to Use in
- When passing value types larger than
IntPtr.Size * 2(typically larger than 16 bytes) in performance-sensitive or high-throughput scenarios. - When pairing the modifier with
readonly structdefinitions to ensure zero-overhead reference passing. - Avoid using
infor reference types (classes) or basic primitives (int,float,enum), as passing a reference to a reference type or a small value type adds indirection without providing any performance advantage.