2

I am currently working on a function where an object (of self written type Catalog) is passed and shall not be mutated / changed in the called function. Here a quick example.


public override bool Migrate(in Catalog fromCatalog)
{
    fromCatalog.SupplierId = 1; //works, but why
}

I want to strictly undermine this behaviour and disallow mutating any properties of the passed object.

Charlieface
  • 52,284
  • 6
  • 19
  • 43
Patrick Cerny
  • 191
  • 2
  • 8
  • 1
    Then make `SupplierId` property readonly ... Why "in" should not allow mutation ? It's only apply to the parameter itself not its members – Selvin Jul 24 '23 at 09:50
  • 1
    `in` just means the compiler prevents `fromCatalog = ...`, not `fromCatalog.Member = ...` – Mathias R. Jessen Jul 24 '23 at 09:52
  • Well okay, that makes sense. However, how does one undermine the mutating of *any* property without directly specifying them as `readonly`. – Patrick Cerny Jul 24 '23 at 09:53
  • by usnig `readonly record struct` ? – Selvin Jul 24 '23 at 09:55
  • 1
    If you're using structs, they should almost always be implemented as immutable types. If Catalog is a class and you still want the owner to be able to mutate it, then make an interface 'IReadOnlyCatalog' which has public getters for all the properties, and no setters. You can see something similar with 'IList' and 'IReadOnlyList' – Andrew Williamson Jul 24 '23 at 09:57
  • `in` prevents doing `fromCatalog = someOtherCatalog` only – Dmitry Bychenko Jul 24 '23 at 10:06

2 Answers2

2

Because the in modifier applies only to the variable (the parameter in this case). It prevents the parameter from having its value changed to refer to a different object.

public bool Migrate(in Catalog fromCatalog)
{
/* Error CS8331  Cannot assign to variable 'fromCatalog' 
or use it as the right hand side of a ref assignment 
because it is a readonly variable */
    fromCatalog = new Catalog(); 

    fromCatalog.SupplierId = 1;
}

and create a shallow copy of the object so that you don't change anything in the object you passed (within the class)

example:

public Catalog CreateACopy()
{
    Catalog obj = (Catalog)this.MemberwiseClone();
    // and other...
    return obj;
}
        

utilization:

public class Program 
{
    private static void Main()
    {
        Catalog catalog = new Catalog();
        new Program().Migrate(catalog.CreateACopy());
    }
    public bool Migrate(in Catalog fromCatalog)
    {
        fromCatalog.SupplierId = 1;
    }
}
Volodymyr
  • 82
  • 8
  • 3
    "Because the in modifier applies only to the object" - no, it applies to the parameter, *not* the object. – Jon Skeet Jul 24 '23 at 10:27
  • I wanted to say that the in modifier will not allow the passed variable to change the reference inside this method, am I right? – Volodymyr Jul 24 '23 at 10:35
  • 3
    Sort of, although that's not how I'd express it. But it's really, really important to understand the difference between a variable (the parameter in this case) and the object. If the `in` modifier *genuinely* applied to the object, it would prevent changes being made to the object itself, and that's precisely what it *doesn't* do. It prevents the parameter from having its value changed to refer to a different object. – Jon Skeet Jul 24 '23 at 10:54
  • 1
    Adding to Jon's comment, it would be hard for the compiler to accurately prevent changes from being made to the object itself. It's easy to disallow direct member assignments, but the compiler would also have to have a way of distinguishing which methods mutate the object so it can prevent them from being called. That's not as simple as 'any method which mutates the object' because some methods might just be populating a cache – Andrew Williamson Jul 24 '23 at 13:01
1

It works because the Catalog has a writeable property. C# does not have a concept of constant values the same way for example C++ does. So fromCatalog is just another value you can pass around, call methods on, set properties, etc.

But that does not mean it makes much sense to write code like this. The in keyword is mostly useful to avoid copies of a value type. But if the value type is mutable the compiler has to create a defensive copy anyway, just to prevent the original object from being modified.

So the general recommendation is to make your value types immutable/readonly, this avoids the issue. See also why mutable structs are evil.

JonasH
  • 28,608
  • 2
  • 10
  • 23