Yikes, this answer was accepted, and then I changed it. I should probably include the original answer below, since this was accepted by the OP.
New answer
Update : here's what. string absolutely must behave like a reference type. The reasons for this have been affected by all the answers so far: the string type does not have a constant size, it makes no sense to copy the entire contents of the string from one method to another, string[] arrays should otherwise resize themselves - just to name a few.
But you can still define string as a struct that internally points to a char[] array or even a char* and int pointer by its length will make it immutable and voila !, you'd have a type that behaves like a reference type, but technically is a value type.
That would be pretty stupid, honestly. As Eric Lippert noted in several comments on other answers, defining a type of value like this is basically the same as defining a reference type. In almost every sense, it would be indistinguishable from a reference type defined in the same way.
So, the answer to the question "Why is string reference type?" there is, basically: "To do this type of value will be just silly." But if this is the only reason, then, in fact, the logical conclusion is that string can indeed be defined as a struct , as described above, and there would be no particular argument against this choice.
However, there are reasons why it is better to make a string a class than a struct , which are more than purely intelligent. Here is a couple I could think of:
To prevent boxing
If string was a value type, then every time you passed it to a method, waiting for an object , you would have to insert it into the box, which would create a new object that would inflate the heap and make senseless gas pressure. Since the strings are mostly everywhere, their appearance in boxing all the time will be a big problem.
For intuitive comparison comparisons
Yes, string can override Equals regardless of whether it is a reference type or a value type. But , if it was a value type, then ReferenceEquals("a", "a") will return false! . This is due to the fact that both arguments are obtained in the box, and the arguments in the box never have equal references (as far as I know).
So, although it is true that you can define a value type to act just like a reference type, since it consists of one field of the reference type, it still will not be exactly the same. Therefore, I support this as a more complete reason why string is a reference type: you can make it a value type, but it will only burden it with unnecessary flaws.
Original answer
This is a reference type because only references to it are passed.
If it were a value type, then every time you passed a line from one method to another, the whole line would be copied *.
Since this is a reference type, instead of string values like "Hello world!" omitted - "Hello world!" this, by the way, is 12 characters, which means that it requires (at least) 24 bytes of storage - only links to these lines are transmitted. Walking around a link is much cheaper than passing each character in a string.
Also, this is really not an ordinary primitive data type. Who told you?
* Actually, this is not so true. If the string internally holds the char[] array, then as long as the array type is a reference type, the contents of the string will not actually be passed by value - there will only be a reference to the array. I still think this is basically the correct answer.