184

C# 8.0 introduces a convenient way to slice arrays - see official C# 8.0 blogpost.

The syntax to access the last element of an array is

var value = new[] { 10, 11, 12, 13 };

int a = value[^1]; // 13
int b = value[^2]; // 12

I'm wondering why the indexing for accessing the elements backwards starts at 1 instead of 0? Is there a technical reason for this?

NetMage
  • 26,163
  • 3
  • 34
  • 55
Michael Pittino
  • 1,556
  • 4
  • 12
  • 18
  • 17
    Note that C++ ranges are also `[beginInclusive, endExclusive)`. It is a common convention. – bommelding Jan 08 '19 at 13:16
  • 4
    @Sinatr: Based on that blog post, the syntax to return everything would be `value[0..^0]`, since the ending index is exclusive (which is how most other languages work, too). Also, conveniently, `value[^i..^0]` will give you the last `i` items. – BlueRaja - Danny Pflughoeft Jan 08 '19 at 18:13
  • 2
    @bommelding: C++ `rbegin()` somewhat disagrees with that notion -- the first item out of that range isn't the one-beyond-the-end either. ;-) – DevSolar Jan 09 '19 at 12:37
  • 2
    Oh cool, this looks like the equivalent of negative indexing in python: `value[-1] # 13` – cs95 Jan 09 '19 at 18:45
  • 2
    @coldspeed And in Ruby the same as Python. I'm guessing they both borrowed this convention from Perl. – Wayne Conrad Jan 09 '19 at 20:15
  • 1
    It does seem counter-intuitive that `^1` means "the last element" when used as an index, and "the second-to-last element" when used as part of a range. – Joel Mueller Mar 07 '19 at 03:03
  • @JoelMueller In a range, `^1` means the end of the (exclusive) `Range`, or the last element. – NetMage Mar 29 '21 at 21:15

1 Answers1

196

Official answer

Here is a comment from Mads Torgersen explaining this design decision from the C# 8 blog post:

We decided to follow Python when it comes to the from-beginning and from-end arithmetic. 0 designates the first element (as always), and ^0 the “length’th” element, i.e. the one right off the end. That way you get a simple relationship, where an element's position from beginning plus its position from end equals the length. the x in ^x is what you would have subtracted from the length if you’d done the math yourself.

Why not use the minus (-) instead of the new hat (^) operator? This primarily has to do with ranges. Again in keeping with Python and most of the industry, we want our ranges to be inclusive at the beginning, exclusive at the end. What is the index you pass to say that a range should go all the way to the end? In C# the answer is simple: x..^0 goes from x to the end. In Python, there is no explicit index you can give: -0 doesn’t work, because it is equal to 0, the first element! So in Python, you have to leave the end index off completely to express a range that goes to the end: x... If the end of the range is computed, then you need to remember to have special logic in case it comes out to 0. As in x..-y, where y was computed and came out to 0. This is a common nuisance and source of bugs.

Finally, note that indices and ranges are first class types in .NET/C#. Their behavior is not tied to what they are applied to, or even to be used in an indexer. You can totally define your own indexer that takes Index and another one that takes Range – and we’re going to add such indexers to e.g. Span. But you can also have methods that take ranges, for instance.

My answer

I think this is to match the classic syntax we are used to:

value[^1] == value[value.Length - 1]

If it used 0, it would be confusing when the two syntaxes were used side-by-side. This way it has lower cognitive load.

Other languages like Python also use the same convention.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Martin Zikmund
  • 38,440
  • 7
  • 70
  • 91
  • 16
    Minor correction to Mads comment: you do **not** have to leave off the end index completely in python. You can use `None` in place of a number: `[0,1,2,3,4][2:None] == [2,3,4]`. But, yes you cannot use an integer as end index (without computing the length obviously). – Giacomo Alzetta Jan 08 '19 at 17:01
  • 6
    Wait.. what's wrong with `x..`? That seems fine and I've never had problem with the python `[3:]` syntax. – mowwwalker Jan 08 '19 at 21:27
  • 2
    @mowwwalker nothing wrong. I seems that `x..` syntax will be supported too. It's in example of [ranges proposal](https://github.com/dotnet/csharplang/blob/master/proposals/ranges.md) – Mariusz Pawelski Jan 09 '19 at 00:22
  • 9
    @mowwwalker - isn't that already covered in the quote? "So in Python ... If the end of the range is computed, then you need to remember to have special logic in case it comes out to 0" – Damien_The_Unbeliever Jan 09 '19 at 08:26
  • 4
    @mowwwalker Mads comment was about cases in which you do not know what the index value is going to be because it's computed in some way. They are saying that in the case you want to compute `endIndex` as a negative index (i.e. index from the end) you will have a discontinuity between negative and positive numbers because `0` isn't going to work in the correct way in that case. As I pointed out you have to replace `0` by `None` for that. This means your code should look like `seq[startIndex:endIndex or None]` for example. The `or None` should be omitted if `endIndex` is expected to be positive. – Giacomo Alzetta Jan 09 '19 at 13:18
  • 5
    It's good to see they're not repeating Python's mistake with the -0 thing. Handling that special case is a huge hassle and way too easy to forget. – user2357112 Jan 09 '19 at 19:14
  • Martin Zikmund, your addition to the formal answer is a few good insights. Useful! – Alex Fainshtein Aug 07 '22 at 20:29