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
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).
Citation preview
C# Light A proposal for a new C# syntax Inspired by a post by
PhilTrelford UPGRADE YOUR MONITOR Read these slides and you might
get a free triple height monitor* *or visual equivalent
Why change something that works?
Why change something that works? Because... Why not compete
with scripting languages? Lightweight, but keep the power of static
typing.
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.
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).
! 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.
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
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.
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; } }
} Here is a typical immutable class in C# which we'll use as an
example throughout.
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; /// /// Full name ///
public string Name { get { return _name; } } /// /// Birthday ///
public DateTime Birthday { get { return _birthday; } } }
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; /// /// Full name ///
public string Name { get { return _name; } } /// /// Birthday ///
public DateTime Birthday { get { return _birthday; } } }
In a typical C# project less than 50% of the lines contain
useful information! Surely we can do better?
Proposal 1: Simplify doc strings
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;
/// /// 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. 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; } } }
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!
Proposal 2: Automatically create backing fields from
constructor parameters Yes, I know C# 6 has something similar. Keep
reading...
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.
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?
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?
Proposal 3: Merge the primary constructor with the class
definition C# 6 has this too.
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?
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?
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.
Proposal 4: (this one is controversial)
Proposal 4: Use indentation instead of curly braces
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.
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.
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.
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
Observation With these 4 proposals: 27 lines of code has shrunk
to 9 lines!
C# Because "C# Light" means you see 3x more code on your
screen! Why use "C# light"? Reason 1
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; } }
} 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.
Proposal 5: Eliminate syntax noise There is a lot of syntax
"noise" that is not needed, IMO.
Proposal 5a: Eliminate "get" keyword for immutable
properties
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.
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?
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.
Proposal 5b: Eliminate "return"
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.
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?
Proposal 5c: Make immutable properties public by default
public class Person(string name, DateTime birthday) = /// Full
name public string Name = name; /// Birthday public DateTime
Birthday = birthday; The properties are immutable.
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!
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.
Proposal 5d: Make semicolons optional
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.
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?
Observation: Removing syntax noise opens up more opportunities
to save space.
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?
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.
Proposal 6: Use type inference for properties
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?
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!
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".
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.
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.
Review
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
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; /// /// Full name ///
public string Name { get { return _name; } } /// /// Birthday ///
public DateTime Birthday { get { return _birthday; } } } Normal C#
Before and After C# Light 27 lines before. 7 lines after.
What about the goal "make common scenarios easy"?
The next set of proposals aims to do exactly this.
Proposal 7: Automatically generate code for equality
class Person(string name, DateTime birthday) : IEquatable = ///
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?
[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?
Proposal 8: Compact syntax for DTOs
[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?
[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.
Proposal 9: Create non-nullable reference types
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!
Proposal 10: Allow anonymous types to implement interfaces
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.
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?
Proposal 11: Allow subclasses to be merged into a single "case"
class
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?
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:
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!
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.
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.
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.
Proposal 12: Require all properties to be initialized in the
constructor
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; }
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!
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!
Excited by C# Light? Want it now?
But C# Light is available for download right now* C# v6 is not
yet available *syntax not identical to that shown in this
proposal.
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.
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