Upgrade to Pro — share decks privately, control downloads, hide ads and more …

C# Light - A proposal for a new C# syntax

C# Light - A proposal for a new C# syntax

A proposal for a new C# syntax.

Reasons why:

* Why not compete with scripting languages? Lightweight, but keep the power of static typing.
* Times are changing. Defaults should change too. Make immutability the new default.
* Many common patterns are too hard. Make good practices easy (but allow deviations).

Scott Wlaschin

May 23, 2023
Tweet

More Decks by Scott Wlaschin

Other Decks in Technology

Transcript

  1. C# Light A proposal for a new C# syntax Inspired

    by a post by Phil Trelford UPGRADE YOUR MONITOR Read these slides and you might get a free triple height monitor* *or visual equivalent
  2. Why change something that works? Because... Why not compete with

    scripting languages? Lightweight, but keep the power of static typing.
  3. Why change something that works? Because... Why not compete with

    scripting languages? Lightweight, but keep the power of static typing. Because... Times are changing. Defaults should change too. Make immutability the new default.
  4. Why change something that works? Because... Why not compete with

    scripting languages? Lightweight, but keep the power of static typing. Because... Times are changing. Defaults should change too. Make immutability the new default. Because... Many common patterns are too hard. Make good practices easy (but allow deviations).
  5. ! HEALTH WARNING ! These slides are for consumption by

    open-minded programmers ONLY. When taken with pre-existing closed-mindedness, can cause shock, high blood pressure, anxiety, nausea, confusion and panic.
  6. You must be at least this open minded to read

    these slides How open minded are you? Slime mould Bony fishes People who don't use LINQ People who don't use generics Opposable thumbs People who want C# v6 People who use lambdas
  7. Vision for C# Light Clean and lightweight code. Immutability by

    default. Common scenarios are easy. We make mutability a special case, rather than the other way around.
  8. public class Person { public Person(string name, DateTime birthday) {

    _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// <summary> /// Full name /// </summary> public string Name { get { return _name; } } /// <summary> /// Birthday /// </summary> public DateTime Birthday { get { return _birthday; } } } Here is a typical immutable class in C# which we'll use as an example throughout.
  9. Here is a typical immutable class in C# which we'll

    use as an example throughout. 27 lines of code. Can we do better? public class Person { public Person(string name, DateTime birthday) { _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// <summary> /// Full name /// </summary> public string Name { get { return _name; } } /// <summary> /// Birthday /// </summary> public DateTime Birthday { get { return _birthday; } } }
  10. Here is a typical immutable class in C# which we'll

    use as an example throughout. 27 lines of code. Can we do better? Why not remove the lines that don't contain useful information? public class Person { public Person(string name, DateTime birthday) { _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// <summary> /// Full name /// </summary> public string Name { get { return _name; } } /// <summary> /// Birthday /// </summary> public DateTime Birthday { get { return _birthday; } } }
  11. In a typical C# project less than 50% of the

    lines contain useful information! Surely we can do better?
  12. We can start by simplifying the doc strings. Let's make

    "summary" the default. public class Person { public Person(string name, DateTime birthday) { _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// <summary> /// Full name /// </summary> public string Name { get { return _name; } } /// <summary> /// Birthday /// </summary> public DateTime Birthday { get { return _birthday; } } }
  13. We can start by simplifying the doc strings. Let's make

    "summary" the default. public class Person { public Person(string name, DateTime birthday) { _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// <summary> /// Full name /// </summary> public string Name { get { return _name; } } /// <summary> /// Birthday /// </summary> public DateTime Birthday { get { return _birthday; } } }
  14. public class Person { public Person(string name, DateTime birthday) {

    _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// Full name public string Name { get { return _name; } } /// Birthday public DateTime Birthday { get { return _birthday; } } } We can start by simplifying the doc strings. Let's make "summary" the default. 4 lines saved!
  15. public class Person { public Person(string name, DateTime birthday) {

    _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// Full name public string Name { get { return _name; } } /// Birthday public DateTime Birthday { get { return _birthday; } } } Because immutability is the default, we can simplify further.
  16. public class Person { public Person(string name, DateTime birthday) {

    _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// Full name public string Name { get { return _name; } } /// Birthday public DateTime Birthday { get { return _birthday; } } } Why not automatically define and initialize read- only backing fields from the constructor parameters?
  17. public class Person { public Person(string name, DateTime birthday) {

    } /// Full name public string Name { get { return name; } } /// Birthday public DateTime Birthday { get { return birthday; } } } Of course, you can still define mutable private fields in the usual way. Unlike C# 6, you don't need to in the immutable case. 4 lines saved! Why not automatically define and initialize read-only backing fields from the constructor parameters?
  18. public class Person { public Person(string name, DateTime birthday) {

    } /// Full name public string Name { get { return name; } } /// Birthday public DateTime Birthday { get { return birthday; } } } How often do you have more than one constructor?
  19. public class Person { public Person(string name, DateTime birthday) {

    } /// Full name public string Name { get { return name; } } /// Birthday public DateTime Birthday { get { return birthday; } } } How often do you have more than one constructor? Why not merge the constructor with the class definition?
  20. public class Person(string name, DateTime birthday) { /// Full name

    public string Name { get { return name; } } /// Birthday public DateTime Birthday { get { return birthday; } } } How often do you have more than one constructor? 4 more lines saved! Why not merge the constructor with the class definition? You can still define secondary constructors separately if you need to.
  21. public class Person(string name, DateTime birthday) { /// Full name

    public string Name { get { return name; } } /// Birthday public DateTime Birthday { get { return birthday; } } } Do we really need the braces? The indentation already gives us all the clues we need.
  22. public class Person(string name, DateTime birthday) = /// Full name

    public string Name = get { return name; } /// Birthday public DateTime Birthday = get { return birthday; } Do we really need the braces? The indentation already gives us all the clues we need. 6 more lines saved! You can still have explicit begin/end markers for blocks if you need to.
  23. public class Person(string name, DateTime birthday) = /// Full name

    public string Name = get { return name; } /// Birthday public DateTime Birthday = get { return birthday; } Add "equals" as an indicator to start a new block.
  24. People who complain about using a language with syntactic whitespace

    People who have spent time using a language with syntactic whitespace "Oddly enough, Python's use of whitespace stopped feeling unnatural after about twenty minutes. I just indented code, pretty much as I would have done in a C program anyway, and it worked." - Eric Raymond Helpful Venn Diagram Overlap
  25. C# Because "C# Light" means you see 3x more code

    on your screen! Why use "C# light"? Reason 1
  26. public class Person { public Person(string name, DateTime birthday) {

    _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// <summary> /// Full name /// </summary> public string Name { get { return _name; } } /// <summary> /// Birthday /// </summary> public DateTime Birthday { get { return _birthday; } } } public class Person(string name, DateTime birthday) = /// Full name public string Name = get { return name; } /// Birthday public DateTime Birthday = get { return birthday; } Why use "C# light"? C# C# Light Reason 2 Because "C# Light" means you write 1/3 as much code.
  27. Proposal 5: Eliminate syntax noise There is a lot of

    syntax "noise" that is not needed, IMO.
  28. public class Person(string name, DateTime birthday) = /// Full name

    public string Name = get { return name; } /// Birthday public DateTime Birthday = get { return birthday; } The class is immutable. Every property is "get" only.
  29. public class Person(string name, DateTime birthday) = /// Full name

    public string Name = get { return name; } /// Birthday public DateTime Birthday = get { return birthday; } The class is immutable. Every property is "get" only. So why bother with the "get" keyword?
  30. public class Person(string name, DateTime birthday) = /// Full name

    public string Name = return name; /// Birthday public DateTime Birthday = return birthday; The class is immutable. Every property is "get" only. So why bother with the "get" keyword? A bit cleaner, IMO.
  31. public class Person(string name, DateTime birthday) = /// Full name

    public string Name = return name; /// Birthday public DateTime Birthday = return birthday; Why not take a leaf out of other languages and make the return implicit for the last line in a block? This is the "expression-based" approach.
  32. public class Person(string name, DateTime birthday) = /// Full name

    public string Name = name; /// Birthday public DateTime Birthday = birthday; Why not take a leaf out of other languages and make the return implicit for the last line in a block?
  33. public class Person(string name, DateTime birthday) = /// Full name

    public string Name = name; /// Birthday public DateTime Birthday = birthday; The properties are immutable.
  34. public class Person(string name, DateTime birthday) = /// Full name

    public string Name = name; /// Birthday public DateTime Birthday = birthday; The properties are immutable. Why not make them public by default. There's no way a client of the object can corrupt it!
  35. class Person(string name, DateTime birthday) = /// Full name string

    Name = name; /// Birthday DateTime Birthday = birthday; The properties are immutable. Why not make them public by default. There's no way a client of the object can corrupt it! You can still use the "private" keyword if you need to. Only the default has changed.
  36. class Person(string name, DateTime birthday) = /// Full name string

    Name = name; /// Birthday DateTime Birthday = birthday; How often do you have more than one semicolon on a line? Excepting for loops, of course.
  37. class Person(string name, DateTime birthday) = /// Full name string

    Name = name /// Birthday DateTime Birthday = birthday How often do you have more than one semicolon on a line? So why bother?
  38. class Person(string name, DateTime birthday) = /// Full name string

    Name = name /// Birthday DateTime Birthday = birthday Why not move short code fragments on to the same line?
  39. class Person(string name, DateTime birthday) = /// Full name string

    Name = name /// Birthday DateTime Birthday = birthday Why not move short code fragments on to the same line? C# 6 plans to have "expression- bodied members" too.
  40. class Person(string name, DateTime birthday) = /// Full name string

    Name = name /// Birthday DateTime Birthday = birthday Why do we have to repeat the type? Can't the compiler figure it out for us?
  41. class Person(string name, DateTime birthday) = /// Full name Name

    = name /// Birthday Birthday = birthday Why do we have to repeat the type? Can't the compiler figure it out for us? Cleaner, but now we can't tell that it's a property!
  42. class Person(string name, DateTime birthday) = /// Full name member

    Name = name /// Birthday member Birthday = birthday We need some way to indicate properties. Let's use the word "member".
  43. class Person(string name, DateTime birthday) = /// Full name member

    Name = name /// Birthday member Birthday = birthday /// Age member Age() = DateTime.Today.Subtract(birthday).Days / 365 We can define methods the same way.
  44. class Person(string name, DateTime birthday) { /// Full name public

    string Name { get; } = name; /// Birthday public DateTime Birthday { get; } = birthday; /// Age public int Age() => DateTime.Today.Subtract(birthday).Days / 365; } Here is the C# 6 equivalent with auto-properties and expression-bodied members This is nice and compact too. Still need explicit types and "public" keyword, though.
  45. Let's review the proposals: 1) Make doc strings more compact

    2) Move backing fields into constructor (because immutable) 3) Move constructor into class definition 4) Remove curly braces 5) Remove syntax noise - Remove "get" keyword from immutable properties - Return last value in a block automatically - Make immutable properties public by default - Make semicolons optional 6) Allow types of properties to be inferred from constructor
  46. class Person(string name, DateTime birthday) = /// Full name member

    Name = name /// Birthday member Birthday = birthday public class Person { public Person(string name, DateTime birthday) { _name = name; _birthday = birthday; } private readonly string _name; private readonly DateTime _birthday; /// <summary> /// Full name /// </summary> public string Name { get { return _name; } } /// <summary> /// Birthday /// </summary> public DateTime Birthday { get { return _birthday; } } } Normal C# Before and After C# Light 27 lines before. 7 lines after.
  47. class Person(string name, DateTime birthday) : IEquatable<Person> = /// Full

    name member Name = name /// Birthday member Birthday = birthday override Equals(Person obj) = if (obj == null) return false Person p = obj as Person if ((Person)p == null) { return false } // Return true if the fields match: (name == p.Name) && (birthday == p.Birthday) override Equals(Person p) = if ((object)p == null) return false // Return true if the fields match: (name == p.Name) && (birthday == p.Birthday) override GetHashCode() = name.GetHashCode ^ birthday.GetHashCode How often do you have to write all this equality code?
  48. [StructuralEquality] class Person(string name, DateTime birthday) = /// Full name

    member Name = name /// Birthday member Birthday = birthday Why not let the compiler write it for you! You just need to use a special attribute. How often do you have to write all this equality code?
  49. [StructuralEquality] class Person(string name, DateTime birthday) = /// Full name

    member Name = name /// Birthday member Birthday = birthday "Dumb" objects with no methods (aka DTOs) are very common. Since they have no methods, can we make the syntax even simpler?
  50. [StructuralEquality] class Person(string name, DateTime birthday) = /// Full name

    member Name = name /// Birthday member Birthday = birthday "Dumb" objects with no methods (aka DTOs) are very common. Since they have no methods, can we make the syntax even simpler? class Person = {string name, DateTime birthday} var person = {name="Alice", birthday=Today} Yes, we can. Just define a named class in the same way as an anonymous type.
  51. class Person = {string name, DateTime birthday} // ok var

    person = {name="Alice", birthday=Today} // error person = null This one is a no-brainer. If a class is defined with: • the [StructuralEquality] attribute • the anonymous type syntax then it is automatically non-nullable No more null testing needed!
  52. public class TempDisposable: IDisposable { public void Dispose() { Console.Write("Disposed");

    } } var tempDisposable = new TempDisposable(); Sometimes you don't want to have to create a whole class just to implement an interface temporarily.
  53. var tempDisposable = {new IDisposable with member this.Dispose() = Console.Write("Disposed")

    } Sometimes you don't want to have to create a whole class just to implement an interface temporarily. Why not use the "anonymous type" syntax to create an object without having to create a class explicitly?
  54. Requirement: We accept three forms of payment: Cash. Check or

    Card. For Cash we don't need any extra information For Checks we need a check number For Cards we need a card type and card number This one needs a bit of background. Say that you have a requirement for taking payments, as shown below: How would you implement this?
  55. interface IPaymentMethod {..} class Cash() : IPaymentMethod {..} class Check(int

    checkNo): IPaymentMethod {..} class Card(string cardType, string cardNo) : IPaymentMethod {..} You would probably implement it as an interface and a set of subclasses, like this:
  56. interface IPaymentMethod {..} class Cash() : IPaymentMethod {..} class Check(int

    checkNo): IPaymentMethod {..} class Card(string cardType, string cardNo) : IPaymentMethod {..} But that is a lot of code for such a common scenario. Also... (a) The implementation and data is probably scattered around four separate files. (b) The requirements are hard to reconstruct from the classes. (c) The subclasses are not "closed". Any class that implemented the interface would work. This might not be what you want!
  57. class PaymentMethod = | Cash | Check(int checkNo) | Card(string

    cardType, string cardNo) Answer: Create a "case" or "choice" class that encapsulates all three payment methods in one class, in one place. There are three different constructors, each with different data.
  58. class PaymentMethod = | Cash | Check(int checkNo) | Card(string

    cardType, string cardNo) Answer: Create a "case" or "choice" class that encapsulates all three payment methods in one class, in one place. There are three different constructors, each with different data. PaymentMethod cash = Cash(); PaymentMethod check = Check(123); PaymentMethod card = Card("Visa", "4012888888881881"); The compiler keeps track of which constructor was used to create the instance.
  59. Answer: Create a "case" or "choice" class that encapsulates all

    three payment methods in one class, in one place. void PrintPayment(payment) = switch (payment) { case Cash : // print cash case Check(checkNo) : // print check info case Card(cardType,cardNo) // print card info } A case statement is then used to match the subclass that was created, and at the same time extract the relevant data. class PaymentMethod = | Cash | Check(int checkNo) | Card(string cardType, string cardNo) PaymentMethod cash = Cash() PaymentMethod check = Check(123); PaymentMethod card = Card("Visa", "4012888888881881"); There are three different constructors, each with different data. The compiler keeps track of which constructor was used to create the instance.
  60. Avoid initialization errors. Require all properties to be initialized in

    the constructor. No more properties left as null by mistake! public class Person(string name, DateTime birthday) = /// Full name public string Name = get { return name; } /// Birthday public DateTime Birthday = get { return birthday; }
  61. For example: Compiler error. "birthday" is missing. public class Person(string

    name) = /// Full name public string Name = get { return name; } /// Birthday public DateTime Birthday = get { return birthday; } Avoid initialization errors. Require all properties to be initialized in the constructor. No more properties left as null by mistake!
  62. If they don't have to be initialized, you must make

    them optional. public class Person(string name, DateTime? birthday) = /// Full name public string Name = get { return name; } /// Birthday public DateTime? Birthday = get { return birthday; } Avoid initialization errors. Require all properties to be initialized in the constructor. No more properties left as null by mistake!
  63. But C# Light is available for download right now* C#

    v6 is not yet available *syntax not identical to that shown in this proposal.
  64. Will C# Light work with legacy C# code? C# Light

    will be integrated with Visual Studio. Syntax highlighting, debugging support, and more. What tooling is available for C# Light? Of course. As with any .NET language, assemblies written in C# Light can be mixed with normal C# assemblies, and you can make calls between them.
  65. How can I find out more about C# Light? Full

    details of C# light and how to download it, go to: bit.ly/csharp-light