In C# string datatype has the following properties:
• String is a reference type but behaves like a value type.
• Strings are immutable i.e., every time you change a string then a new string is created and the reference variable changes its reference to the newly created string.
A reference variable to an object type is always passed by reference (might seem tricky but you will get it if you continue reading :)).
But in case of string the reference variable passed by default is passed by value (unless you have explicitly use the ref keyword).
So in the following case:
String hello = "Hello";
MyFunction(hello);
A new reference variable will be created in MyFucntion that refers to the same location as hello string. But when you change the hello function inside the MyFucntion then actually contents of the newly created reference variable will be changed.
void MyFuntion(String hello)
{
//a new string will be created and the reference value of the
//local hello will be changed
hello = "Bye";
}
Let’s get into more details with the help of another example:
Consider the following code:
Inside main:
//let's create a string and pass it to a function as value and reference type
String str = "Hello World";
StringRefernecePassedByValue(str, ref str);
//the reference that was passed by value is the one that is the actual reference
//other one was a new refernece variable that has a copy of the reference
Console.WriteLine("Actual String: " + str);
The function called:
//The body of the function
static void StringRefernecePassedByValue(String valStr,ref String refStr)
{
//Both of the passed reference variable are pointing to the same location
Console.WriteLine("Are equal?: " + ReferenceEquals(valStr, refStr));
//let's change the strings
valStr = "Hello1";//a new string is created and now the valStr refer to it
refStr = "Hello2";//a new string is created and now the refStr refer to it
//let's confirm the output
Console.WriteLine("Passed by Value: " + valStr);
Console.WriteLine("Passed by Reference: " + refStr);
//now both are changed are are no more refering to the same location
Console.WriteLine("Are equal?: " + ReferenceEquals(valStr, refStr));
}
The output is:
Are equal?: True
Passed by Value: Hello1
Passed by Reference: Hello2
Are equal?: False
Actual String: Hello2
Now let’s do the above for a user defined type:
//a user defined datatype
class Student
{
public String name { get; set; }
public String cgpa { get; set; }
}
Inside main:
//let's observe in case of Custom type
Student student = new Student();
student.name = "Ali";
student.cgpa = "3.5";
ClassRefernecePassedByValue(student, ref student);
Console.WriteLine("Actual String: " + student.name);
The function to be called:
//the body of function
static void ClassRefernecePassedByValue(Student valStd, ref Student refStd)
{
//Both of the passed reference variable are pointing to the same location
Console.WriteLine("Are equal?: " + ReferenceEquals(valStd, refStd));
//let's change the objects
valStd.name = "Joseph";//changing the object refered by valStd
refStd.name = "Mick";//changing the object refered by refStd
Console.WriteLine("Passed by Value: " + valStd.name);
Console.WriteLine("Passed by Reference: " + refStd.name);
//now both are same
Console.WriteLine("Are equal?: " + ReferenceEquals(valStd, refStd));
}
The output:
Are equal?: True
Passed by Value: Mick
Passed by Reference: Mick
Are equal?: True
Actual String: Mick
Thus concluding that the reference to a string when passed to a function actually creates a new reference variable which contains a copied reference to another string reference variable but as string is an immutable type thus when the string pointed by the new reference variable changes then actual reference variable stays unchanged.

Also for further understanding you can get the complete code here