8

Recently VS hinted me to use the:

var str = "foo";
var str2 = str[^2..];

Instead of the:

var str = "foo";
var str2 = str.Substring(str.Length - 2);

So, my question is are there any differences between the str[^2..] and the str.Substring(str.Length - 2)? I think it is a new C# feature, but I was not able to find any documentation about it. So, I do not know how it is called and what version of C# does it come with.

Here is what I was trying to google:

^ in string access c#

I did not get any related results. Should I google something else?

manymanymore
  • 2,251
  • 3
  • 26
  • 48
  • 2
    [C# 8.0 Index from end operator (^) and range operator (..)](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#indices-and-ranges) – Rafalon Apr 29 '21 at 09:10
  • 3
    When used with a string, [it compiles to `.Substring`](https://sharplab.io/#v2:EYLgtghglgdgNAExAagD4AEBMBGAsAKHQGYACLEgYRIG8CT6zT0AWEgWQAoBKGuh/gG4QATiQDOAF1EBeEgCIAZgHslcgNx9+9IaMnDMJWXoDaAPUwA6CwF0N+fgF8CDoA==), so there's no difference: it's just neater. With other types it compiles to different things (normally a `Slice` method). [Introduced in C# 8](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/ranges) – canton7 Apr 29 '21 at 09:12
  • 1
    This kind of tendencies of C# frightens me. – Serg Apr 29 '21 at 09:12
  • 1
    @Rafalon, that definitely helps. Should I remove or update my question to be helpful for others? – manymanymore Apr 29 '21 at 09:15

2 Answers2

7

Others are correct in that it is just syntax sugar but there is some slight differences. They both call "Substring" however the hat version calls the String.Substring(Int32, Int32) signature and the later conventional version calls String.Substring(Int32) signature.

It's interesting that even though str[^2..] produces more code it seems to have slightly better performance in my quick benchmark.

var str = "foo";
var str2 = str[^2..];
========generates the following IL=============
   nop  
   ldstr    "foo"
   stloc.0     // str
   ldloc.0     // str
   dup  
   callvirt String.get_Length ()
   dup  
   ldc.i4.2 
   sub  
   stloc.2  
   ldloc.2  
   sub  
   stloc.3  
   ldloc.2  
   ldloc.3  
   callvirt String.Substring (Int32, Int32)
   stloc.1     // str2
   ldloc.1     // str2
   call Console.WriteLine (String)
   nop  
   ret

And the following conventional version.

var str = "foo";
var str2 = str.Substring(str.Length - 2);
========generates the following IL=============
   nop  
   ldstr    "foo"
   stloc.0     // str
   ldloc.0     // str
   ldloc.0     // str
   callvirt String.get_Length ()
   ldc.i4.2 
   sub  
   callvirt String.Substring (Int32)
   stloc.1     // str2
   ldloc.1     // str2
   call Console.WriteLine (String)
   nop  
   ret  
SunsetQuest
  • 8,041
  • 2
  • 47
  • 42
  • 2
    Those two differences are connected. [`Substring(startIndex)` calls through to `Substring(startIndex, Length - startIndex)`](https://referencesource.microsoft.com/#mscorlib/system/string.cs,1263). So by having the generated code do that work while it's already determined the value of `Length`, and call the latter directly, you avoid an unnecessary pass-through call (with an extra `Length` property access), which yields a very slight performance bump. – StriplingWarrior Sep 27 '21 at 21:15
  • @StriplingWarrior - Got it. Thank you for filling me in on that.. and very well explained. You definitely have this stuff down! – SunsetQuest Sep 28 '21 at 05:31
3

Commenters did a great job on this, but I'm gonna put an answer here so the question can be considered answered.

There is no difference. It's just syntax sugar. Here's what LINQPad shows you'd get in "C# 1.0" terms:

   string str = "foo";
   int length = str.Length;
   int num = length - 2;
   int length2 = length - num;
   string str2 = str.Substring (num, length2);
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315