Reputation: 2887
In C++, if I remember correctly, any parameters to a function are passed by value unless qualied by &
which indicates to pass by reference.
For example,
int double ( int i )
{
int twice = i * i;
return twice;
}
int main ( )
{
int i = 4;
int j = double(i);
return 0;
}
is equivalent to
int i = 4;
// --- here's the part where the function runs ---
int temp = i;
int twice = temp * temp;
i = twice;
// -----------------------------------------------
and it would work the same way in C# since int
is a data type or value type (I've heard both terms used).
Where things get different is when you pass in what would be called a reference type or object in C#. In C++, if you pass in a class
without qualifying with &
, the function works as if it's using a deep copy of the parameter.
For example, the following in C++ does not increment Charlie
's Age
.
class Dude
{
pulic Dude ( name = "", age = 0 ) : Name(name), Age(age) { }
public string Name;
public int Age;
}
public void IncrementDudeAge ( Dude dude )
{
++dude.Age;
}
int main ( )
{
Dude Charlie = new Dude ( "Charlie", 69 );
IncrementDudeAge(Charlie);
std::cout << Charlie.Age; // prints 69
}
The main
is equivalent to
Dude Charlie = new Dude ( "Charlie", 69 );
Dude temp = new Dude( "Charlie", 69 );
++temp.Age;
std::cout << Charlie.Age; // prints 69
This is different than C# because in C# the same code would increment the Age
:
public class Program
{
public static void IncrementDudeAge ( Dude dude )
{
++dude.Age;
}
public class Dude
{
public string Name { get; set; }
public int Age { get; set; }
}
public static void Main ()
{
Dude Charlie = new Dude() { Name = "Charlie", Age = 69 };
IncrementDudeAge(Charlie);
Console.WriteLine(Charlie.Age); // prints 70
}
}
That bit of code is equivalent to C++ code like
class Dude
{
pulic Dude ( name = "", age = 0 ) : Name(name), Age(age) { }
public string Name;
public int Age;
}
public void IncrementDudeAge ( Dude * dude )
{
++*dude.Age;
}
int main ( )
{
Dude Charlie = new Dude ( "Charlie", 69 );
IncrementDudeAge(&Charlie);
std::cout << Charlie.Age; // prints 70
}
In other words, in the above 2 pieces of code you are passing in an alias for Charlie
, a copy of something that points to Charlie
. In C++, that alias is an actual integral type, while in C# you can just think of it as an alias.
Because in both cases it is a copy of something that points to Charlie
, you will not affect the actual Charlie
if you try to reassign him:
public void IncrementDudeAge ( Dude * dude )
{
dude = new Dude ( "Charlie", 70 );
}
and
public static void IncrementDudeAge ( Dude dude )
{
dude = new Dude() { Name = "Charlie", Age = 70 };
}
would not do anything. That's where using a ref
in C#, or **
or *&
in C++, would be needed.
Am I correct about everything?
Upvotes: 0
Views: 86
Reputation: 369594
tl;dr: Yes.
I am not intimately familiar with C++, but AFAICS, your explanation looks correct.
In C♯, there are two independent axis to consider:
Note that the word "reference" is used twice with two different meanings above. If we wanted to remove the word, we could approximately rewrite it as follows:
Pass-by-value is the default in C♯. If you don't do anything special, Argument passing is always pass-by-value, without exceptions. You can request pass-by-reference if you explicitly annotate both the definition and the call site with the ref
keyword. (There's also the out
keyword which is a special kind of pass-by-reference intended for output parameters where the callee is not allowed to dereference and is required to initialize the reference in every possible return path.)
So, the difference between pass-by-value and pass-by-reference is in how the argument is being passed.
The difference between value types and reference types is in what is being passed: with value types, the value itself is being passed, with reference types, a pointer to the value is being passed. Since reference types always work with pointers, and value types always without, there is no special syntax for referencing or dereferencing pointers, all of that is implicit. But it is there.
This gives us four possible combinations:
struct MutableCell
{
public string value;
}
class Program
{
static void ArgumentPassing(string[] foo, MutableCell bar, ref string baz, ref MutableCell qux)
{
foo[0] = "More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
foo = new string[] { "C# is not pass-by-reference." };
bar.value = "For value types, it is *not* call-by-sharing.";
bar = new MutableCell { value = "And also not pass-by-reference." };
baz = "It also supports pass-by-reference if explicitly requested.";
qux = new MutableCell { value = "Pass-by-reference is supported for value types as well." };
}
static void Main(string[] args)
{
var quux = new string[] { "Yes, of course, C# *is* pass-by-value!" };
var corge = new MutableCell { value = "For value types it is pure pass-by-value." };
var grault = "This string will vanish because of pass-by-reference.";
var garply = new MutableCell { value = "This string will vanish because of pass-by-reference." };
ArgumentPassing(quux, corge, ref grault, ref garply);
Console.WriteLine(quux[0]);
// More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.
Console.WriteLine(corge.value);
// For value types it is pure pass-by-value.
Console.WriteLine(grault);
// It also supports pass-by-reference if explicitly requested.
Console.WriteLine(garply.value);
// Pass-by-reference is supported for value types as well.
}
}
Upvotes: 3
Reputation: 33566
Yes, you are correct. Some tiny wording issues could be pointed out, but all observations are ok.
Upvotes: 2