2

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?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Does this answer your question? [How to write an async method with out parameter?](https://stackoverflow.com/questions/18716928/how-to-write-an-async-method-with-out-parameter) – Rizquuula Dec 14 '21 at 03:57
  • You are missing an `await`, otherwise, your code does what you think it does. – Tanveer Badar Dec 14 '21 at 04:03
  • Generally, `async` methods are much more useful if they do something naturally asynchronous (for example do some I/O or dispatch work to another thread). Doing an assignment asynchronously (and without `await`ing it just adds complication to a simple operation – Flydog57 Dec 14 '21 at 04:13
  • 3
    You can't have `ref` or `out`, meaning you can't replace the variable `obj = new {...}` and see that change from the caller. – Jeremy Lakeman Dec 14 '21 at 04:19
  • 2
    If you want to know what happens when this code runs you should probably *run this code and see what it does*. That's going to give you a more reliable answer than asking other people what they think it might do. You'll *know* what it does. – Servy Dec 14 '21 at 15:43
  • 1
    You don't need to use `async/await` when changing an object fields **Only** – Muaath Dec 14 '21 at 17:23

3 Answers3

0

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

DekuDesu
  • 2,224
  • 1
  • 5
  • 19
  • Reference types are not necessarily mutable, no. And just because something has a reference to an instance doesn't mean it can be mutated. Immutable reference are extremely common. – Servy Dec 14 '21 at 15:46
  • I completely agree. However, most user defined types, particularly the one the question is defining, is mutable. Furthermore information regarding immutable reference types such as `string` are covered in the MSDN I provided. I felt this was covered clearly by the inclusion of *generally* always mutable. – DekuDesu Dec 14 '21 at 22:02
  • But they *aren't* generally *always* mutable. They're *sometimes* mutable. No always. Saying you included a link that mentioned that your answer is wrong doesn't make the answer *less* wrong. – Servy Dec 15 '21 at 00:25
  • Thanks again, I appreciate your feedback. Where I feel we have a difference in opinion is the boundary of the scope of the question. Where I feel that the question's scope is rather narrow and *general* and a simple answer lacking some nuance is acceptable, I think you may feel that it requires more nuance and less over generalization. I respect your opinion and took another jab at providing more nuance. – DekuDesu Dec 15 '21 at 06:41
  • That's odd, because your answer is covering numerous things that are only very tangentially related to the question asked. You appear to be overgeneralizing it by a lot to me. You are also providing factually inaccurate statements in your tangents, which I have pointed out. That they're wrong *and* not needed to answer the question just makes it worse. – Servy Dec 15 '21 at 13:05
  • Eh you're probably right. If you feel this still doesn't answer the original question, please be sure to submit an answer so OP can have this question answered. Thanks again for the feedback! – DekuDesu Dec 15 '21 at 13:21
  • The OP doesn't need anyone to post an answer to know what their code does when they run it They just need to run their code. That you failed to provide a correct answer to what happens when their code is run is rather interesting (is running the code really that hard?) but regardless I'm confident in the OP's ability to do it entirely on their own. If they have a question being just what the output of the code is then they can asks such a question. – Servy Dec 15 '21 at 13:36
-1

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.

Pieterjan
  • 2,738
  • 4
  • 28
  • 55
  • This is false. All parameters are passed by value unless you use `ref/out/in`. The variable itself may store a reference, if it's a reference type, but it is still passed by value. The distinction is relevant. – Servy Dec 16 '21 at 16:49
  • Check the fiddle – Pieterjan Dec 16 '21 at 16:53
  • It specifically demonstrates that you're wrong. `string` is a reference type, and the caller didn't observe mutations to the variable from within the method. That is because it is passed by value, despite being a reference type. Mutating an object that a variable of a reference type refers to only ever involves reading the variable's value, not changing the variable, and is thus unaffected by whether it is passed by reference or by value. – Servy Dec 16 '21 at 16:56
  • That's why the answer states **with exception of string**. Class objects are clearly passed by reference – Pieterjan Dec 16 '21 at 16:59
  • String is not an exception. If you mutate the variable of **any reference type** it won't be observed by the caller unless you use `ref/out`. String is not special. Nothing about it is different. The difference is that for the method in which you used a string you change the variable, and for the other method you mutated a referenced instance, and didn't mutate the variable. Your implementations are different, not the semantics of the variable. Classes are not passed by reference without the use of `ref/out`, as your own code shows. – Servy Dec 16 '21 at 17:06
  • Perhaps it would be easier to state that `class` objects are passed by reference, and any other types (value types, strings, and `struct` objects) are passed by value, and are copied in memory? Update answer like this? – Pieterjan Dec 16 '21 at 17:50
  • That is not true. Parameters using `ref`/`out/`in` are passed by reference (regardless of their type). All other parameters are passed by value (regardless of their type). `string` is a class. It's a reference type. It's not some special type in this context (the only thing special about it in the language is string literals allowing you to create instance of it in a unique way, which doesn't affect this behavior). – Servy Dec 16 '21 at 17:52
-2

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.

Muaath
  • 579
  • 3
  • 19