In C#, why is String a reference type that behaves

2019-09-15 03:00发布

问题:

A String is a reference type even though it has most of the characteristics of a value type such as being immutable and having == overloaded to compare the text rather than making sure they reference the same object.

Why isn't string just a value type then?

回答1:

Strings aren't value types since they can be huge, and need to be stored on the heap. Value types are (in all implementations of the CLR as of yet) stored on the stack. Stack allocating strings would break all sorts of things: the stack is only 1MB for 32-bit and 4MB for 64-bit, you'd have to box each string, incurring a copy penalty, you couldn't intern strings, and memory usage would balloon, etc...

(Edit: Added clarification about value type storage being an implementation detail, which leads to this situation where we have a type with value sematics not inheriting from System.ValueType. Thanks Ben.)



回答2:

It is not a value type because performance (space and time!) would be terrible if it were a value type and its value had to be copied every time it were passed to and returned from methods, etc.

It has value semantics to keep the world sane. Can you imagine how difficult it would be to code if

string s = "hello";
string t = "hello";
bool b = (s == t);

set b to be false? Imagine how difficult coding just about any application would be.



回答3:

The distinction between reference types and value types are basically a performance tradeoff in the design of the language. Reference types have some overhead on construction and destruction and garbage collection, because they are created on the heap. Value types on the other hand have overhead on method calls (if the data size is larger than a pointer), because the whole object is copied rather than just a pointer. Because strings can be (and typically are) much larger than the size of a pointer, they are designed as reference types. Also, as Servy pointed out, the size of a value type must be known at compile time, which is not always the case for strings.

The question of mutability is a separate issue. Both reference types and value types can be either mutable or immutable. Value types are typically immutable though, since the semantics for mutable value types can be confusing.

Reference types are generally mutable, but can be designed as immutable if it makes sense. Strings are defined as immutable because it makes certain optimizations possible. For example, if the same string literal occurs multiple times in the same program (which is quite common), the compiler can reuse the same object.

So why is "==" overloaded to compare strings by text? Because it is the most useful semantics. If two strings are equal by text, they may or may not be the same object reference due to the optimizations. So comparing references are pretty useless, while comparing text are almost always what you want.

Speaking more generally, Strings has what is termed value semantics. This is a more general concept than value types, which is a C# specific implementation detail. Value types have value semantics, but reference types may also have value semantics. When a type have value semantics, you can't really tell if the underlying implementation is a reference type or value type, so you can consider that an implementation detail.



回答4:

This is a late answer to an old question, but all other answers are missing the point, which is that .NET did not have generics until .NET 2.0 in 2005.

String is a reference type instead of a value type because it was of crucial importance for Microsoft to ensure that strings could be stored in the most efficient way in non-generic collections, such as System.Collection.ArrayList.

Storing a value-type in a non-generic collection requires a special conversion to the type object which is called boxing. When the CLR boxes a value type, it wraps the value inside a System.Object and stores it on the managed heap.

Reading the value from the collection requires the inverse operation which is called unboxing.

Both boxing and unboxing have non-negligible cost: boxing requires an additional allocation, unboxing requires type checking.

Some answers claim incorrectly that string could never have been implemented as a value type because its size is variable. Actually it is easy to implement string as a fixed-length data structure using a Small String Optimization strategy: strings would be stored in memory directly as a sequence of Unicode characters except for large strings that would be stored as a pointer to an external buffer. Both representations can be designed to have the same fixed length, i.e. the size of a pointer.

If generics had existed from day one I guess having string as a value type would probably have been a better solution, with simpler semantics, better memory usage and better cache locality. A List<string> containing only small strings could have been a single contiguous block of memory.



回答5:

Not only strings are immutable reference types. Multi-cast delegates too. That is why it is safe to write

protected void OnMyEventHandler()
{
     delegate handler = this.MyEventHandler;
     if (null != handler)
     {
        handler(this, new EventArgs());
     }
}

I suppose that strings are immutable because this is the most safe method to work with them and allocate memory. Why they are not Value types? Previous authors are right about stack size etc. I would also add that making strings a reference types allow to save on assembly size when you use the same constant string in the program. If you define

string s1 = "my string";
//some code here
string s2 = "my string";

Chances are that both instances of "my string" constant will be allocated in your assembly only once.

If you would like to manage strings like usual reference type, put the string inside a new StringBuilder(string s). Or use MemoryStreams.

If you are to create a library, where you expect a huge strings to be passed in your functions, either define a parameter as a StringBuilder or as a Stream.



回答6:

Also, the way strings are implemented (different for each platform) and when you start stitching them together. Like using a StringBuilder. It allocats a buffer for you to copy into, once you reach the end, it allocates even more memory for you, in the hopes that if you do a large concatenation performance won't be hindered.

Maybe Jon Skeet can help up out here?



回答7:

It is mainly a performance issue.

Having strings behave LIKE value type helps when writing code, but having it BE a value type would make a huge performance hit.

For an in-depth look, take a peek at a nice article on strings in the .net framework.



回答8:

How can you tell string is a reference type? I'm not sure that it matters how it is implemented. Strings in C# are immutable precisely so that you don't have to worry about this issue.



回答9:

Actually strings have very few resemblances to value types. For starters, not all value types are immutable, you can change the value of an Int32 all you want and it it would still be the same address on the stack.

Strings are immutable for a very good reason, it has nothing to do with it being a reference type, but has a lot to do with memory management. It's just more efficient to create a new object when string size changes than to shift things around on the managed heap. I think you're mixing together value/reference types and immutable objects concepts.

As far as "==": Like you said "==" is an operator overload, and again it was implemented for a very good reason to make framework more useful when working with strings.



回答10:

In a very simple words any value who has a definite size can be treated as a value type.



回答11:

Isn't just as simple as Strings are made up of characters arrays. I look at strings as character arrays[]. Therefore they are on the heap because the reference memory location is stored on the stack and points to the beginning of the array's memory location on the heap. The string size is not known before it is allocated ...perfect for the heap.

That is why a string is really immutable because when you change it even if it is of the same size the compiler doesn't know that and has to allocate a new array and assign characters to the positions in the array. It makes sense if you think of strings as a way that languages protect you from having to allocate memory on the fly (read C like programming)



回答12:

At the risk of getting yet another mysterious down-vote...the fact that many mention the stack and memory with respect to value types and primitive types is because they must fit into a register in the microprocessor. You cannot push or pop something to/from the stack if it takes more bits than a register has....the instructions are, for example "pop eax" -- because eax is 32 bits wide on a 32-bit system.

Floating-point primitive types are handled by the FPU, which is 80 bits wide.

This was all decided long before there was an OOP language to obfuscate the definition of primitive type and I assume that value type is a term that has been created specifically for OOP languages.