A couple of problems here.
First, because Strings are immutable, changing the value of a string
doesn't really change the string _in situ_ in memory. Instead, it
creates a whole new string and points the String reference to it.
When you say:
String a = "Old Value";
a = "New Value";
you haven't really changed what is at the memory location where the
string "Old Value" lives. It's still there, if only for a short time
before the garbage collector comes around to sweep it up. What has
happened is that a whole new object in memory, a String instance, has
been created with the value "New Value". The variable a, which contains
a pointer (or a _reference_) to a string, is then changed to refer to
the new String instance (with the value "New Value") instead of the old
one (with the value "Old Value").
Now let's look at what happens when two String variables are involved:
String a = "Old Value";
String b = a;
a = "New Value";
After the first line executes, there is a new String instance in the
heap with the value "Old Value", and the variable "a" contains a
reference to the memory location on the heap where that String instance
is stored.
The second line just copies the reference to the String instance with
the value "Old Value" from "a" to "b". Now both String variables have
the same value, but be careful what I mean by "value": their value is
_not_ a String "Old Value". Their value is a _reference_ or a pointer
to a memory location at which is found a String with the value "Old
Value".
The third line instantiates a new String instance in the heap and gives
it the value "New Value". It then changes the String variable "a" to
contain a reference to this new String instance. Notice that nothing
has happened to the variable "b" in the third line of code: "b" is
still a reference to the first instance of String, which has the value
"Old Value". "a" and "b" now point to different memory locations.
This is _exactly_ what happens with reference types:
ArrayList x = new ArrayList();
ArrayList y = x;
x = new ArrayList();
x.Add("Hello world");
The first line of code creates and empty array list and points "x" to
it. The second line of code points "x" and "y" to the same array list.
The third line of code points "x" to a new array list that is distinct
from the first one. The last line modifies the second array list to
contain one item: a reference to a string with the value "Hello world".
The array list pointed to by "y" will still be the original, empty one,
so x.Count will be 1, but y.Count will be 0.
The different with the String type is that you don't see that "new"
operation taking place. It happens behind the scenes, which can be
confusing.
On top of this, there is one _true_ difference between Strings and most
other reference types that _does_ make Strings look like value types:
comparing two Strings with the "==" operator compares their _values_,
not their addresses (references), whereas comparing two object
references with "==" usually compares addresses, not values.
The second bit of confusion in your question relates to the fact that
objects are not, in fact, _passed by reference_ to methods. Instead,
object references are passed by value, which is exactly what happens
with String. To understand the difference, read Jon Skeet's excellent
page on parameter passing in C#:
http://www.yoda.arachsys.com/csharp/parameters.html
Suffice to say that your question would have been more interesting if
in your method changeString() you had said:
field.Replace("l", "i");
and asked why the resulting output was not "Oid Vaiue" instead of "Old
Value". The problem with your original example is that setting "field"
to refer to a new object would _never_ change testField back in Main,
regardless of what kind of object "field" was declared to be. The only
way to make that happen is to do this:
changeString(ref testField);
....
private static void changeString(ref String field) ...
You will then see that yes, in fact testField changes its value in Main
after changeString completes.