Write an async method with object as parameter

Viewed 918

I wonder if after calling this async method, it will change the data of the object parameter.

public async Task AsyncMethod(ExampleObject obj)
{
    obj.number = 10;
}

public class ExampleObject
{
    public int number = 0;
}

static void Main(string[] args)
{
    ExampleObject obj = new ExampleObject();
    AsyncMethod(obj);
    Console.WriteLine(obj.number);
}

Because I saw someone told You can't have async methods with ref or out parameters. But if I pass the reference type, will it change data after calling this async method?

3 Answers

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 classes(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

First of all:

Reference types (like classes, with exception of string) are passed By Reference. So when parameters (of reference type) are modified inside the method, the value is changed in every frame of the call stack. Every frame inside the call stack holds a pointer to the same object in the memory stack.

Value types (like structs and int, double, float, ...) are passed By Value. So each time a method is called, a deep copy is made of the objects and the new pointers are passed to the method. So when parameters (of value type) are modified inside the method, you're actually modifying the copy of the parameter.

Here's a Fiddle with the most prominent cases.

Note that string objects are by default passed By Value.


Since c# 8.0 you don't need Tuples or stuff like that anymore. You can simply use the following notation:

async Task<(int, string)> func()

Meaning that you can easily create an async method which returns as many values as you wish, without the need of a wrapper class.

No, It won't change. Because you are passing the parameter by coping, Not by reference.

And in async methods, You can't pass parameters by reference.

Or you can return a the edited object like this:

public class ExampleObject
{
    public int number = 0;
}

// Here you'll got a warning from C# because there is no async calls inside this method
public static async Task<ExampleObject> AsyncMethod(ExampleObject obj)
{
    obj.number = 10;
    return obj;
}

static async Task Main(string[] args)
{
    ExampleObject obj = new ExampleObject();
    obj = await AsyncMethod(obj); // Here you missed "await" in your code
    Console.WriteLine(obj.number); 
}

Anyway, You don't need to use async when you only want to change fields.

Related