I wonder if after calling this async method, it will change the data of the object parameter.
Yup it sure does, with some minor modifications such as adding an await and making main
async
your code compiles and gives the exact result you suspected, 10.
The reason you can change the value of the field int number
from the async method without adding ref
is because you passed a reference type by-value as an argument for AsyncMethod
.
There are two kinds of types: value types and reference types†.
With value types, each variable has its own copy of the data, and it is not possible for operations on one variable to affect the other (except in the case of in, ref and out parameter variables).
With reference types, two variables can reference the same object; therefore, operations on one variable can affect the object referenced by the other variable.
When you pass any variable as an argument for a method you can pass it as a value type or a by-reference type(ref
).
What you can do with that variable inside of the method once you have passed it is determined by how you passed that variable to the method, among many other things.
When you pass a value type as a value type, such as:
struct MyStruct{
public int number;
}
void Method(MyStruct obj){ ... }
The value-type is essentially duplicated and the called method receives a copy. So any way you change the value-type will not affect the original MyStruct
that you used to call Method
.††
However, when you pass a value-type such as MyStruct
by reference (ref
), the address(the original MyStruct
) is sent to called method††, and any change to MyStruct
will reflect back on the original MyStruct
that you sent to the method.
For reference types it's a little bit different.
When you pass a reference type by value such as:
public class ExampleObject
{
public int number = 0;
}
void Method(ExampleObject obj){ ... }
you're not making a copy and giving it to Method
, rather a reference to the data [inside of the ExampleObject
] is sent to called method††. But when you pass a reference type by reference such as Method(ref ExampleObject obj)
the address(the location of the original variable) of the reference sent to called method.††
When you have a reference or address of a variable you can access it's members provided those members are visible and accessible to the caller(not private
for example).
The big difference here is, with a reference you can access or mutate its members. Providing those members are either accessible†††† fields with no modifiers that prevent mutability(such as readonly
) or properties that have an accessible†††† getters and setters get;set;
.
By default all user-defined class
es(not primitives like int
for example) that have concrete implementations(not abstract
for example) and have accessible and assignable members(like fields with no modifiers, or properties with getters and setters) are mutable. This means changes on the classes instanced members will reflect back on the original variable if you have a reference or address to it. Whether or not the original variable can be reassigned to depends on whether or not it's explicitly prevented with things such as readonly
, init
etc.
However with an address you can not only access it's member's, you could also reassign that variable to something else entirely, and the original variable will be affected as well, this functionality of reassignment applies to value-types as well. Any assignment to the corresponding parameter actually modifies the corresponding caller’s variable, field, or array element.†††
These properties of passing arguments do not change with async methods
, the only thing that changes is whether a by-reference(ref
) parameter is allowed, which in the case of async
defined methods, they are not among other things such as in
, out
. Why these are prevented are outside the scope of this question, but generally these restrictions are to prevent transient and intermediary objects from breaking their lifetime contracts among many other things and reasons.
But if I pass the reference type, will it change data after calling this async method?
In your example yes, and it does! You can do this because you have a reference to the original object(passed as an argument), and the field int number
is accessibly and mutable because you have no modifiers that explicitly prevent mutability(such as readonly
, const
etc..). All standard fields with no special modifiers and are accessible are mutable, at least for reference types such as the one in your question. Properties such as public int number {get;set;}
do not share this default mutability. All properties default to having a getter and an optional setter and are not by-default mutable unless explicitly defined.†††††
† I.8.2.1
†† I.12.4.1.5.4
††† I.12.4.1.5.2
†††† I.8 .5 .3 .2
††††† I.8.6