Understanding the 'in' keyword in C#

The in keyword in C# is a powerful yet often overlooked feature that can enhance both the performance and safety of your code. Introduced in C# 7.2, it addresses specific scenarios where you want to pass parameters efficiently while ensuring they remain unmodified.

What is the in Keyword?

The in keyword is used to pass parameters by reference while enforcing that the method cannot modify the parameter. It’s particularly useful for large structs (to avoid copying) and scenarios where you want to guarantee immutability for parameters. Consider a struct with 10 double fields. Passing it with in can reduce method call overhead by avoiding a 80-byte copy (vs. a 4/8-byte reference).

void ProcessData(in MyLargeStruct data)
{
    // data cannot be modified here.
}

How Does in Work?

Example 1: Avoiding Struct Copying

Consider a large struct representing a 3D point

public struct Point3D
{
    public double X, Y, Z;
}

Without in, passing this struct to a method creates a copy and is very expensive for large struct.

double CalculateDistance(Point3D p1, Point3D p2)
{
    // Creates copies of p1 and p2. Expensive for large structs!
    return Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + ...);
}

With in, the struct is passed by reference, avoiding the copy:

double CalculateDistance(in Point3D p1, in Point3D p2)
{
    // Uses references to p1 and p2. No copies made!
    return Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + ...);
}

Example 2: Immutability Enforcement

Attempting to modify an in parameter results in a compile-time error

void UpdateValue(in int value)
{
    value = 42; // Compiler error: Cannot assign to 'value'.
}

Use Cases

1. Memory Management in Game Dev

In game engines like Unity, structs such as Vector3 (which contains X, Y, Z coordinates) are frequently used. Passing these with in avoids unnecessary copying in performance-critical loops

public float CalculateMagnitude(in Vector3 vector)
{
    return MathF.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z);
}

2. High-Performance Data Processing

When processing large datasets (e.g., scientific computing), using in ensures structs are passed efficiently

public void ProcessFrame(in VideoFrame frame)
{
    // Analyze frame without modifying it.
}

3. Preventing Accidental Modifications

For reference types, in prevents reassigning the reference (though the object’s data can still change):

void LogData(in List<string> data)
{
    // data.Add("new item"); // Allowed (modifies the list).
    // data = new List<string>(); // Error: Cannot reassign the reference.
}