First of all, I'm sure you realize you are going to get a lot of NullReferenceException with that code you've got there.
Secondly, the string class is a fun and special object which you can mentally think of as a primitive data type. Let me just say though, System.String is not a primitive type.... but you can think about it like it is for general purposes.
I have drawn some diagrams that may better display what is going on in each of the examples you gave.
When you
string s1 = "Hello";
string s2 = s1;
An object named s1 of type string is created which points to a memory space that holds the value "Hello". Then a new object of type string named s2 is created and the value of "Hello" from s1 is copied to another memory space and addressed by s2. This results in your memory looking as it does below.

Now, when you
s1 = null;
You have set the reference of s1 to point to the null pointer, which leaves that memory with the value of "Hello" to float around until the garbage collector comes around and removes it. At this point s1 is still pointing to the value that was copied earlier.

Now lets look at how this differs from a normal object.
When you
Name s1 = new Name();
s1.id = 5;
Name s2 = s1;
A memory space called s1 is created which points to a newly allocated space in memory the large enough to hold the memory of a Name object.
Then you pointed to the memory space addressed in s1 and changed its Property called id to be 5.
Then you created a new memory space called s2 which copies the value of the address to the allocated memory mentioned above. So now s1 and s2 point to the same object in memory. This results in your memory looking as it does below.

Note: That at this point, if you were to change the value of id it would change for both s1 and s2, and if that was nullable type you were using for id, and you set it to be null, the change would be reflected in both s1 and s2.
Now if you were to
s1 = null;
as you did in your post. s1 would change its address to point to the null pointer, and s2 would continue to be looking at the same space in memory. Which results in what you see below.

Edit: To provide further explanation on why strings seem to behave like primitives and not like objects.
I don't know, however I will speculate.
My guess is that it was designed this way because developers want to use string as if it were a primitive. I will say that it is so nice to not have to worry about stray references to my string, or having to clone it before I make changes to it. However Microsoft could not make string a primitive type because strings are huge. The max length of a string is the maximum value of an Int32. And every letter is a char which is a Int16, so that is somewhere around 2 gigs I believe (don't quote my math). This means that strings are too big to store on the stack which is only 1MB. Thus strings have to be objects and placed on the heap.
If you are unaware of the differences between the stack and the heap, I do recommend looking it up, it is good to know how memory is handled, while in C# all that dirty work is done for you, if you decide to move to something like C++ you'll find yourself managing that on your own (not super fun).
So in short I suppose it is because we want to use strings like primitives, but they are implemented as objects because of the limitations of the stack.
s2is not null but you can't see it becauses1.idthrowsNullReferenceException. RemoveConsole.WriteLine("s1 = " + s1.id);and you will see5in the Console.s1 = nullyou are only setting that variables1to null, you are not changing anything thats1used to refer to. And as @Selman22 said, if you remove the null ref exception, you'll see they both behave the same. Also, try actually running the code first.