C# can call it that specifically if it likes, because the general computer science term is dead, but under the hood you're passing a reference by value. Look to the generated assembler in a non-inlined function. You'll find a copy of a pointer. You did not in true pass-by-refernce langauges.
The fact that is a sensible thing to say in a modern language is another sign the terminology is dead.
C#'s ref parameters, same as C++' s reference types, have true pass-by-reference semantics. Whether this gets compiled to pass-by-pointer or not is not observable from the language semantics.
That is, the following holds true:
int a = 10;
foo(ref a) ;
Assert(a == 100);
void foo(ref int a)
{
a = 100;
}
There's also a good chance that in this very simple case that the compiler will inline foo, so that it will not ever pass the address of a even at the assembly level. The same would be true in C++ with `void foo (int& a)`.
Right, and on the flip side, Forth on say x86 will inevitably involve passing a pointer to the stack, be it explicitly or implicitly (ie global variable).
So if one invokes low-level implementation details, Forth is also a pass-by-pointer-value in the same way as C# "ref" and others, at least on x86.
However I don't think appealing to implementation details is useful, what matters is what is observed through the language, with the results you point out.