Preface

C# is (and will continue to be) my favourite general-purpose language. In fact I wouldn't be writing this post if I didn't care about C#.

I'm not making these points because I think I definitely know better. They're just concerns that I haven't seen adequately addressed thus far. I actually semi-hope that someone will reply to this with reasons why these concerns are ill-founded.

There are also some really exciting features planned for C# 8 in my opinion, including non-nullable references, record types, async streams, improvements to pattern matching, and more; none of which I'm going to be talking about below.

Range Operator and 'Hat' Operator

The Proposal

public static void Main() { var intArray = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var arraySubrange = intArray[3..7]; // Array subrange is now a 'view' on to intArray that includes the values 3, 4, 5, 6 var lastElement = intArray[^1]; // lastElement is 9 }

View Code Fullscreen • "Slicing and Hat Operator Proposed Syntax"

3..7

IEnumerable<T>

^1

My Concern

var arraySubrange = intArray[startIndex..endIndex]

intArray[3...7]

3, 4, 5, 6

7

intArray[^1]

intArray[^0]

const int ArraySize = 10; public static void Main() { var intArray = new[ArraySize] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var arraySubrange = intArray[0..ArraySize]; arraySubrange = intArray[0..^0]; // Same result }

exclusive

inclusive

ArraySize

const int ArraySize = 10; public static void Main() { var intArray = new[ArraySize] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var arraySubrange = intArray[0..ArraySize-1]; arraySubrange = intArray[0..^0]; // Same result }

count-1

// With inclusive indexing public static bool IsPalindrome(string input) { for (int i = 0; i < input.Length; i++) { if (input[i] != input[^i]) return false; } return true; } // With exclusive indexing public static bool IsPalindrome(string input) { for (int i = 0; i < input.Length; i++) { if (input[i] != input[^(i + 1)]) return false; } return true; }

var reversed = intArray.Reverse().ToArray()

reversed[0]

intArray[^1]

intArray[^0]

intArray[^0]

intArray[0..^0]

0..0

i..i+n

i..i+n-1

Default Interface Methods

The Proposal

My Non-Concerns

And lots more

The 'meaning' of an interface does not change. It is-and-remains a construct that forward-declares a set of methods any class must implement for its instances to be usable as the thing the interface represents. Just because a certain method on an interface has a default implementation shouldn't mean a thing to outside consumers of that interface. Only the implementer of the interface need worry.

no state (properties are not state, they are methods that look like state) and therefore retain a completely distinct and useful value in C#. Abstract classes can contain state, and classes will remain only-singularly-inheritable. If they did not, other issues would arise (such as the infamous diamond of death ). Interfaces containstate (properties are not state, they are methods that look like state) and therefore retain a completely distinct and useful value in C#.

Multiple Inheritance is not in itself always a bad thing , and received a bad name in the wild-west days of C++; but traits are a form of MI and are used daily to great success.

My Concerns

interface IEntity { // interface representing an entity in a game world Vector3 Position { get; } } interface ISoundEmittingEntity : IEntity { // represents any entity that emits sound float SoundRadius { get; } // method with a default (sensible) implementation; can be overridden for more complex cases bool IsAudibleToPlayer(Player player) => Vector3.Distance(player.Position, Position) <= SoundRadius; } class Rocket : ISoundEmittingEntity { public Vector3 Position { get; private set; } public float SoundRadius { get; } = Units.InMetres(4f); } // ---- void SomeMethod(Rocket rocket) { if (rocket.IsAudibleToPlayer()) PlaySound(Sounds.RocketSound); }

IsAudibleToPlayer

Rocket

class Rocket : ISoundEmittingEntity { public Vector3 Position { get; private set; } public float SoundRadius { get; } = Units.InMetres(4f); public bool IsAudibleToPlayer(Player player) => ISoundEmittingEntity.base.IsAudibleToPlayer(player); }

interface IEntity { Vector3 Position { get; } } interface ISoundEmittingEntity : IEntity { float SoundRadius { get; } bool IsAudibleToPlayer(Player player); } public static class SoundEmittingEntityDefaultImplProvider { public static bool DefaultIsAudibleToPlayer(ISoundEmittingEntity @this, Player player) { return Vector3.Distance(player.Position, @this.Position) <= @this.SoundRadius; } } class Rocket : ISoundEmittingEntity { public Vector3 Position { get; private set; } public float SoundRadius { get; } = Units.InMetres(4f); public bool IsAudibleToPlayer(Player player) => this.DefaultIsAudibleToPlayer(player); }

IRocket

Rocket

Rocket

Drop the whole claim of support for traits entirely from this proposal, and embrace fully the fact that this isn't the primary objective, or...

Revisit its applicability as a traits implementation and at least allow us some kind of syntax to auto-import default members (e.g. something like [ImportDefaults(typeof(ISoundEmittingEntity))] class Rocket : ISoundEmittingEntity ), if not hopefully making it the standard behaviour.

Edit 15th Sep '18:

Edit 15th Sep '18:

Edit 15th Sep '18:

Edit 16th Sep '18:

In the following post I'm going to talk about two concerns I have with features planned for C# 8. Beforehand though, I just want to point out a couple of things:Also for previous subscribers, I'm sorry for the huge hiatus in blogging. I've been busy with work and all my spare time has been going in to patching my game . That's finally done however so hopefully normal service will be resumed. :)Anyway, let's crack on...The proposal is essentially adding two new operators to C#:The range operator () selects a subrange of the indexed collection. At the moment the consensus is that the returned object would be some kind of struct that implements; and that it would be simply a 'view' on to the source collection, and not invoke a copy operation of the targeted elements.The hat operator () indexes the target collection from the end of the sequence, rather than the start.Firstly, I think the overall proposal is generally a good thing. I'm not convinced it's something people are crying out for; although if they decide to allow non-constants in the range expression (e.g.) I will certainly re-evaluate that opinion.My issue is that both the hat operator and the end-element of the range operator are currently 'exclusive'. I.e.returns a view onto the elementsbut; and to get the last element of an array the correct syntax isI think the main justification for this is to intuitively support the following scenarios:Withindexing, the code above works very nicely; whereas withindexing you need a -1 on the, I personally think that's a pattern that many veteran programmers are familiar with. Yes, it's less terse than its alternative, but I think there is already a collective understanding amongst coders that collections end atFurthermore, thedefault currently being proposed makes other algorithms less intuitive. Take this example of a function that determines whether a string is a palindrome (taken from Joe4evr ):For me, this example perfectly highlights the underlying issue: Programmers are used to collections being 0-based, and violating that assumption is counter-intuitive, even if we're talking about a new operator.In other words, just because we're indexing from the back doesn't mean we should go against that habitual behaviour that sequences start at 0.Another example: If I reverse a sequence (e.g. with) I can then get the last element from the original array with. But if I access the elements from the back with the hat-operator, I have to useDiscussion is actually still ongoing about this on github, but I got the chance to ask Mads Torgersen (the C# Language Project Manager) about this via the recent .NET Conf live stream; and his reply indicated thatis currently planned to point past the end of the array (i.e.will always throw an IndexOutOfBoundsException).One justification he gave is that this means the range expressionwill intuitively return the entire array. But my contention is that this is a kind of circular logic; if the hat and range operator were both inclusive, you'd still get the same nice result. The problem would only occur if the hat-operator was inclusive while the range operator remained exclusive.One side-note I'd like to add is that I don't see this as a particularly cut-and-dry issue. I've tried to present the case both for and against these operators being inclusive, and I'm very open to the idea that someone might show me a convincing reason I'm wrong. So please, do form your own opinions. I'm also not particularly comfortable being in the realm of 'disagreeing with Mads Torgersen' about something. Oh well, if you don't stick your neck out sometimes you'll never see over the fence... :)There are also good arguments against my concerns. See for example this Reddit comment by /u/TheBuzzSaw "It's pretty important that the range be inclusive-begin exclusive-end. So, an empty range is. [...] Having an exclusive-end makes many many operations really simple. When processing fixed width chunks, you simply add the chunk size:. Having an inclusive-end would result in."No prizes for guessing this one was gonna come up. To be honest, I think most community-invested C# programmers are well aware of this proposal and already have strong opinions on it. If you don't know what it is, simply follow the links above.Firstly let me start by saying I'm actually generally on-board with the idea. This should be a way of getting traits in to C#, which is something that many, many other modern languages already have:Some people have argued that it changes the meaning of what an interface is, and/or that we already have abstract classes for this purpose, and/or that it's a form of multiple inheritance...There are also other issues some people have which I can't claim to have an opinion on; I suggest reading through some of the comments on the tour post It's really only one concern, but it's a big one for me: This proposal is being touted as an implementation of traits for C#, but in my opinion it's a lousy one. Why? Because. Here's an example:The problem with the code above is that it won't compile.has to be explicitly imported in to, even if it's just the default implementation.Currently, the proposed syntax for this would be:Frankly I don't find this particularly useful, and it doesn't really resemble any traits implementation I'm familiar with. We can already implement an almost identical pattern with what's available today:In fact, with extension methods we can actually get even closer to real traits, as I demonstrated 2 years ago The current response from Microsoft is that we should use interfaces as our 'leaves' (i.e. usingeverywhere instead of; and then only using the actualclass to instantiate).But that doesn't make much sense to me. I don't see why it's any better: We still have to awkwardly re-import default behaviour somewhere, and yet the leaf interfaces still look as though it's done automatically. Why?Furthermore, if you envision an order of magnitude more leaves than trait types that's probably way more trouble than it's worth.And finally, as a way of reducing boilerplate/code duplication, existing patterns like the ones I enumerated above already exist and are already more terse, imo.Personally, it feels to me like the default interface methods proposal is actually primarily there to support an entirely different use-case: Allowing interfaces to be modified after going public, and compatibility with languages like Java (which as far as I understand, would make Xamarin users' lives a lot easier). And what I wish Microsoft would do is either:Thanks for reading!Jared Par confirmed on Reddit that non-constants will be permitted in range expressionsFixed an error in the traits code (replaced IsAudibleToPlayer with DefaultIsAudibleToPlayer)Fixed an error in showing inclusive vs exclusive differences for range selectionAdded a link to /u/TheBuzzSaw's reddit comment in the side-note subheading of the range operator section