(file under “stuff that I knew and forgot but wish I hadn't and probably won't ever again”)
Parameters in C# are, by default, passed by value.
That means that your function cannot modify the value of its arguments. You pass it an int i = 2 and even if internally it does i = 3, once the function returns you still have i == 2.
However for reference types, pass by value does not mean what you might think it means. A reference type is basically a memory address. That memory address is passed (sure enough) by value - the function cannot change the location of your object. But internal to the function the reference type is still pointing to the same location in memory. Thus any changes to the object within the function will be reflected in the calling context - the function will have side effects.
So what do I find in some of our code today?
void function (User user){
User localUser = user;
localUser.SomeProperty = "Not the value that you were expecting haha hoho";
}
Hrm. My calling context's value for SomeProperty is not what I was expecting. Seems someone had realised that the reference type was going to cause problems... “I know, I will make a copy of it and change that.” Unfortunately that will not work either. The assignment operator copies the value of the reference type (the memory address) not the object itself. I.e. it is not a deep copy.
How do you fix this? You make a function that performs a deep copy, and call it from within any function that receives your reference type as a parameter.
A good place for this function? A copy constructor. An overloaded constructor in the reference type class that takes one argument - a reference to an object of the same type. Then copies all of the properties to the new instance.
class User{
public string UserName;
User(User existingUser){
UserName = existingUser.UserName;
}
}
How do you enforce the “no messing with my data” rule? Well, the only thing that I can think of is “If you don't want people breaking your toys, don't lend them out” - only let them have a copy.
function(new User(MyUser));
What does explicitly telling a reference type to be passed by reference actually do? Not much as far as I can see.
Would it be nice if passing a reference type by value did something closer to what you might expect? (Could be done by internally making a deep copy of the object at the time of the call) Possibly.
Will I ever forget that “Reference types are always modifiable when passed as parameters”? I wish I could say no.