Using 'in' C# Keyword as a Parameter Modifier

February 18, 2025#Software Development
Article
Author image.

Scott DePouw, Senior Consultant

C#, like most programming languages, has a plethora of features and functionality that would take a long time to fully encompass. I’ve been using C# professionally for over 15 years now and am still learning new things about the language (including features that have been around for a while). A recent example would be the subject of this very blog post: Using the in keyword as a parameter modifier.

The most I’ve done with parameter modifiers has been decorating TryParse()’s second parameter with the out keyword, to set a strongly-typed variable if the parsing succeeds.

// Integer variable "id" initialized with the value of 5
bool parsed = int.TryParse("5", out int id);
if (parsed)
{
  _someRepository.GetById(id);
}

Beyond that I’ve not created anything with the out keyword, nor have I ever used other parameter modifiers such as ref or in.

After discovering some of what in can allow, however, I plan to fold it into my toolbox of useful features C# has to offer. Let’s explore what it does, how you can use it, and when one should probably avoid using it!

A Primer on Using the in Parameter Modifier

To use the in parameter modifier, simply decorate any parameters with the keyword in your method declaration:

public interface ILogger
{
  // Interface and implementation must match all parameter modifiers.
  public void LogMessage(in string message);
}

public class ConsoleLogger : ILogger
{
  public void LogMessage(in string message) => Console.WriteLine(message);
}

You don’t have to explicitly declare your passed arguments as in, but you can if you want:

public class Example
{
  private readonly ILogger _logger = new ConsoleLogger();
  
  public void DoStuff()
  {
    string myMessage = "Hello, World!";
    _logger.LogMessage(myMessage);
    _logger.LogMessage(in myMessage);
    _logger.LogMessage("Some Inline String");
    
    // Compiler error: "An expression cannot be used in this context
    // because it may not be passed or returned by reference"
    // _logger.LogMessage(in "Some Inline String");
  }
}

There is an occasion where you must supply the in keyword, however: When you overload your method to have a version with in and a version without it:

public interface ILogger
{
  public void LogMessage(in string message);
  public void LogMessage(string message);
}

public class Example
{
  private readonly ILogger _logger = new ConsoleLogger();
  
  public void DoStuff()
  {
    string myMessage = "Hello, World!";
    // Calls version with "in"
    _logger.LogMessage(in myMessage);
    // Inline strings won't be able to call the version with "in"
    // _logger.LogMessage(in "Some Inline String");
    
    // Calls version without "in"
    _logger.LogMessage(myMessage);
    _logger.LogMessage("Some Inline String");
  }
}

Off the top of my head I can’t think of a reason to overload methods in this manner (but I’m sure there are use cases). But if you ever need to, the capability exists!

Now that we know the syntax, let’s dive into why we’d want to use the in parameter modifying keyword.

Pass By Readonly Reference

When a method modifies a parameter with the in keyword, the variable is passed in by readonly reference. Normally, value types (such as int) have their value copied over to the parameter (“pass by value”), and reference types (such as string) are passed by not-readonly reference. Using the in modifier changes the behavior.

  • Value types are now passed by reference (and are readonly), instead of “pass by value”
  • Reference types are now passed by readonly reference

The “readonly” aspect is the game-changer when using in. When using this modifier, attempting to change the input parameter in any way results in a compiler error!

public void LogMessage(in string message)
{
  // Compiler error: "Cannot assign to variable 'message' or use it as the
  // right hand side of a ref assignment because it is a readonly variable"
  message = "Changing!";
}

For primitive value types, this doesn’t add much. Since value types are copied, it doesn’t matter what you do with the copied value within the method. The calling code will never know or care that the copied value was modified. We’ll get to a use case for using in on value types later, but one takeaway here is that you don’t really ever need to use in on primitive value types (such as int), since there’s no real benefit to it.

Reference types, though? That’s another story! How many times have you worked in an application that had some method passing in a string or some other reference type, only for the method to unintentionally modify the parameter by side effect? I know I’ve run into it on several occasions. Wouldn’t it be nice to guarantee that when you call a method, you know that it cannot modify your reference types?

This is what in lets you do!

public void LogMessage(in string message) => Console.WriteLine(message);

// When using the above method, we know that our precious string cannot be changed!
string myPreciousString = GetPreciousString();
_logger.LogMessage(myPreciousString);

// Safely rely on the fact that myPreciousString is not changed.

We Can Fix Primary Constructors Now, Right?

You now have the power to declare parameters immutable. Perfect! One of my biggest gripes about C# 12’s new Primary Constructors feature is that the primary constructor values are not readonly. Lame! This means that this class

public class SomeClass
{ 
  // _logger cannot be modified after construction
  private readonly ILogger _logger;
  
  public SomeClass(ILogger logger) => _logger = logger;
}

is not the same as this class (despite what IDEs may lead you to believe by suggesting this as a “code fix”):

public class SomeClass(ILogger logger)
{
  // I can freely modify "logger" any time I want!
}

But we can solve that problem now, right? Just use our shiny new toy, in! …Right?

public class SomeClass(in ILogger logger)
{
  // Unfortunately, this doesn't work!
}

While you can declare your class with a primary constructor like this, you cannot then use the in-decorated parameter. Attempting to do so results in a compiler error:

public class SomeClass(in ILogger logger)
{
  public void DoStuff()
  {
    // Error: "Cannot use ref, out, or in primary constructor
    // parameter 'logger' inside an instance member"
    logger.LogMessage("Foo");
  }
}

There does appear to be some buzz within Microsoft about allowing users to declare these primary constructor parameters readonly in the future. Unfortunately, we cannot use in to solve this big issue with primary constructors presently.

But enough about what we can’t do. Let’s take a look at a use case for the in modifier for value types: Efficiency!

Passing Large Value Objects By Reference

Earlier I noted that it’s not too useful to pass primitive value types (such as int) using in: There’s no benefit to your code, and it adds complexity for no real gain.

However, what if you have a value object that has a large footprint? A struct, after all, is passed by value. What if you have code that is called rapidly, or otherwise must be super performant? It’d be convenient if we had a way to not copy around large value instances, while still maintaining the benefit of “pass by value” immutability.

in lets us do just that!

public struct SomeStruct
{
  public int Id { get; set; }
  // Imagine a lot more things in here.
}

public interface ISomeProcess
{
  void Process(in SomeStruct structToProcess);
}

public class Example(ISomeProcess someProcess)
{
  public void DoSomethingEfficiently(SomeStruct myStruct)
  {
    // struct is passed by readonly reference, preventing
    // us from making expensive copies in memory.
    someProcess.Process(myStruct);
  }
}

The struct is passed to Process() by readonly reference. The implementation of Process() cannot modify the given struct, but it also didn’t have to copy the entire struct in order to accomplish this. Using in is saving us a lot of memory usage!

Summary

  • The in parameter modifier keyword allows us to change how method parameters are passed. Value types and reference types alike both change from “pass by value” and “pass by reference”, respectively, into “pass by readonly reference.”
  • Use the in parameter modifier when you want to guarantee that a reference type will not be modified by the method being called.
  • Use the in parameter modifier when you have large value types that would be expensive to copy in memory, to improve efficiency.
  • Do not use the in parameter on primitive value types (such as int).
  • We cannot use the in parameter to fix the “not readonly” issue present with primary constructors.

References


Copyright © 2025 NimblePros - All Rights Reserved