string.Empty is a static readonly field, not a literal constant. This precludes its use anywhere that requires a compile-time constant, e.g. attribute constructors and default parameters. This is... annoying, to put it mildly. (It also allows the use of reflection to change the value, useful if you hate yourself and/or your colleagues.)
There have been numerous unanswered questions over the years as to why this member was declared as a field and not a literal, for example:
The general consensus seems to be that string, being one of the earliest classes in the Framework, was created before interning was implemented and thus at the time, the use of a field was necessary to prevent duplication of the value. This is supported by a comment by Eric Lippert on a now-deleted answer on that second question:
I asked one of the C# old-timers over lunch about this and he did not recall specifically why this decision was made, but conjectured that it had something to do with interning.
A second possibility/reason is in the comment on the field itself: https://referencesource.microsoft.com/#mscorlib/system/string.cs,73
We need to call the String constructor so that the compiler doesn't mark this as a literal.
Marking this as a literal would mean that it doesn't show up as a field which we can access
from native.
This seems to imply that constants cannot be accessed from native code, which doesn't make much sense to me (although I am hardly an expert on native). Even Jon Skeet doesn't know: https://stackoverflow.com/questions/8462697/what-are-native-and-literal-keywords/8462706#comment10464457_8462706
I don't know - it doesn't make much sense to me, to be honest. I believe it's talking about access from native code, rather than support for native types, but I don't know the details behind the comment.
At the end of the day my questions is pretty simple:
Empty had never existed on System.String and was being considered for addition today, would it be added as a field or a const? Keeping in mind the (apparent) necessity for access from native code.string.Empty to be a const now? (I suspect that the answer may differ between Framework and Core, if so please provide that information.)string.Empty is a static readonly field, not a literal constant. This precludes its use anywhere that requires a compile-time constant, e.g. attribute constructors and default parameters.
You can always use "" if you need a constant empty string.
If Empty had never existed on System.String and was being considered for addition today, would it be added as a field or a const?
Perhaps a better question is if it would be added at all, you need a reason for that. One of the few reasons to add it would be to avoid creating a bunch of empty string instances, similar to Array.Empty<T>. But then making it a const would defeat the purpose.
Points mentioned in the original issue in the csharplang repo by @YairHalberstadt:
I don't know if anyone is using reflection on string.Empty, but if they are, this would be a breaking change.
More worryingly, it would be a binary (but not source) breaking change on pretty much every single project.
You can always use
""if you need a constant empty string.
I'm well aware - but I don't want to. I consider literal "" a magic string and would prefer to avoid using it at all. We have decimal.Zero that is const and works perfectly, why can't we do the same for strings?
Perhaps a better question is if it would be added at all, you need a reason for that.
Stylistic more than anything, but it's also always bugged me that string.Empty is static readonly while e.g. decimal.Zero is const - why the discrepancy?
decimal.Zero is a valuetype, so doesn't allocate anyway, hence there are no advantages to a static readonly field.
I have the same question too. However, making it switch from static-readonly to const is a breaking change and folks probably won't approve it.
I consider literal "" a magic string and would prefer to avoid using it at all
Magic string is something like "reporting-queue". For that you define your own constant named ReportingQueueName and use it in the code instead of the string, so you can easily change the name if needed. An empty string may be a magic string, for which again you create your own constant - for example, const string DefaultFileName = "";. Using string.Empty instead of "" achieves nothing in the context of magic strings.
We have decimal.Zero that is const and works perfectly
It's not const. The C# compiler/language is lying to you. The runtime does not support struct typed constants. In reality decimal.Zero is a static readonly field with some fancy attribute on it so it behaves like a constant, at least as far as C# is concerned.
Also, there's no Int32.Zero or anything like that.
but it's also always bugged me that string.Empty is static readonly while e.g. decimal.Zero is const - why the discrepancy?
See above.
String.Empty is treated as intrinsic by the runtime and the JIT. It works exactly same as "" underneath. Both will give you pointer to the empty string singleton and the JITed code will look same for both as well. It is just a style matter which one you use.
making it switch from static-readonly to const is a breaking change and folks probably won't approve it.
Correct. These are mistakes of the past that we rather patch by doing extra work to compensate where possible.
Also, there's no Int32.Zero or anything like that.
There is readonly static IntPtr.Zero. It is also treated as intrinsics so all IntPtr.Zero, new IntPtr(0) or (IntPtr)0 are pretty much equivalent performance wise.
Most helpful comment
Magic string is something like "reporting-queue". For that you define your own constant named
ReportingQueueNameand use it in the code instead of the string, so you can easily change the name if needed. An empty string may be a magic string, for which again you create your own constant - for example,const string DefaultFileName = "";. Usingstring.Emptyinstead of""achieves nothing in the context of magic strings.It's not const. The C# compiler/language is lying to you. The runtime does not support struct typed constants. In reality
decimal.Zerois astatic readonly fieldwith some fancy attribute on it so it behaves like a constant, at least as far as C# is concerned.Also, there's no
Int32.Zeroor anything like that.See above.