The underlying thing always is: value types are passed by value, and reference types are "passed by reference" (quoted because the value of the reference is actually passed by value, but most people ignore that for the sake of brevity).
The easiest way to reconcile the ref
keyword against references is: reference types have their reference passed by value. This has the effect, in the standard case, of simply passing the reference to the list (and not the entire list) to the method.
The ref
keyword, when used on a reference type, semantically passes a reference to the reference (I really struggle not to say "pointer to a pointer").
If your method were to re-assign the ref
argument to a new object, the caller would also see this new assignment. Whereas without the ref
keyword, the method would simply be re-assign their own local copy of the reference value and the caller would still have a reference to their original object.
The above explanation is shamelessly taken from Jon Skeet's article on the topic:
This difference is absolutely crucial to understanding parameter
passing in C#, and is why I believe it is highly confusing to say that
objects are passed by reference by default instead of the correct
statement that object references are passed by value by default.
The ref
keyword is only needed if you intend to re-assign the argument and have that visible to the caller. In most cases you will find that it isn't needed. Your DoStuff
can be re-written to remove it and still pass a reference to the list by value successfully:
void DoSomething(List<string> strs)
{
strs.Add("Hello");
}