Welcome to C# 9.0

Mads Torgersen

Mads

C# 9.0 is taking shape, and I’d like to share our thinking on some of the major features we’re adding to this next version of the language.

With every new version of C# we strive for greater clarity and simplicity in common coding scenarios, and C# 9.0 is no exception. One particular focus this time is supporting terse and immutable representation of data shapes.

Let’s dive in!

Init-only properties

Object initializers are pretty awesome. They give the client of a type a very flexible and readable format for creating an object, and they are especially great for nested object creation where a whole tree of objects is created in one go. Here’s a simple one:

new Person
{
    FirstName = "Scott",
    LastName = "Hunter"
}

Object initializers also free the type author from writing a lot of construction boilerplate – all they have to do is write some properties!

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

The one big limitation today is that the properties have to be mutable for object initializers to work: They function by first calling the object’s constructor (the default, parameterless one in this case) and then assigning to the property setters.

Init-only properties fix that! They introduce an init accessor that is a variant of the set accessor which can only be called during object initialization:

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

With this declaration, the client code above is still legal, but any subsequent assignment to the FirstName and LastName properties is an error.

Init accessors and readonly fields

Because init accessors can only be called during initialization, they are allowed to mutate readonly fields of the enclosing class, just like you can in a constructor.

public class Person
{
    private readonly string firstName;
    private readonly string lastName;
    
    public string FirstName 
    { 
        get => firstName; 
        init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
    }
    public string LastName 
    { 
        get => lastName; 
        init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
    }
}

Records

Init-only properties are great if you want to make individual properties immutable. If you want the whole object to be immutable and behave like a value, then you should consider declaring it as a record:

public data class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

The data keyword on the class declaration marks it as a record. This imbues it with several additional value-like behaviors, which we’ll dig into in the following. Generally speaking, records are meant to be seen more as “values” – data! – and less as objects. They aren’t meant to have mutable encapsulated state. Instead you represent change over time by creating new records representing the new state. They are defined not by their identity, but by their contents.

With-expressions

When working with immutable data, a common pattern is to create new values from existing ones to represent a new state. For instance, if our person were to change their last name we would represent it as a new object that’s a copy of the old one, except with a different last name. This technique is often referred to as non-destructive mutation. Instead of representing the person over time, the record represents the person’s state at a given time.

To help with this style of programming, records allow for a new kind of expression; the with-expression:

var otherPerson = person with { LastName = "Hanselman" };

With-expressions use object initializer syntax to state what’s different in the new object from the old object. You can specify multiple properties.

A record implicitly defines a protected “copy constructor” – a constructor that takes an existing record object and copies it field by field to the new one:

protected Person(Person original) { /* copy all the fields */ } // generated

The with expression causes the copy constructor to get called, and then applies the object initializer on top to change the properties accordingly.

If you don’t like the default behavior of the generated copy constructor you can define your own instead, and that will be picked up by the with expression.

Value-based equality

All objects inherit a virtual Equals(object) method from the object class. This is used as the basis for the Object.Equals(object, object) static method when both parameters are non-null.

Structs override this to have “value-based equality”, comparing each field of the struct by calling Equals on them recursively. Records do the same.

This means that in accordance with their “value-ness” two record objects can be equal to one another without being the same object. For instance if we modify the last name of the modified person back again:

var originalPerson = otherPerson with { LastName = "Hunter" };

We would now have ReferenceEquals(person, originalPerson) = false (they aren’t the same object) but Equals(person, originalPerson) = true (they have the same value).

If you don’t like the default field-by-field comparison behavior of the generated Equals override, you can write your own instead. You just need to be careful that you understand how value-based equality works in records, especially when inheritance is involved, which we’ll come back to below.

Along with the value-based Equals there’s also a value-based GetHashCode() override to go along with it.

Data members

Records are overwhelmingly intended to be immutable, with init-only public properties that can be non-destructively modified through with-expressions. In order to optimize for that common case, records change the defaults of what a simple member declaration of the form string FirstName means. Instead of an implicitly private field, as in other class and struct declarations, in records this is taken to be shorthand for a public, init-only auto-property! Thus, the declaration:

public data class Person { string FirstName; string LastName; }

Means exactly the same as the one we had before:

public data class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

We think this makes for beautiful and clear record declarations. If you really want a private field, you can just add the private modifier explicitly:

private string firstName;

Positional records

Sometimes it’s useful to have a more positional approach to a record, where its contents are given via constructor arguments, and can be extracted with positional deconstruction.

It’s perfectly possible to specify your own constructor and deconstructor in a record:

public data class Person 
{ 
    string FirstName; 
    string LastName; 
    public Person(string firstName, string lastName) 
      => (FirstName, LastName) = (firstName, lastName);
    public void Deconstruct(out string firstName, out string lastName) 
      => (firstName, lastName) = (FirstName, LastName);
}

But there’s a much shorter syntax for expressing exactly the same thing (modulo casing of parameter names):

public data class Person(string FirstName, string LastName);

This declares the public init-only auto-properties and the constructor and the deconstructor, so that you can write:

var person = new Person("Scott", "Hunter"); // positional construction
var (f, l) = person;                        // positional deconstruction

If you don’t like the generated auto-property you can define your own property of the same name instead, and the generated constructor and deconstructor will just use that one.

Records and mutation

The value-based semantics of a record don’t gel well with mutable state. Imagine putting a record object into a dictionary. Finding it again depends on Equals and (sometimes) GethashCode. But if the record changes its state, it will also change what it’s equal to! We might not be able to find it again! In a hash table implementation it might even corrupt the data structure, since placement is based on the hash code it has “on arrival”!

There are probably some valid advanced uses of mutable state inside of records, notably for caching. But the manual work involved in overriding the default behaviors to ignore such state is likely to be considerable.

With-expressions and inheritance

Value-based equality and non-destructive mutation are notoriously challenging when combined with inheritance. Let’s add a derived record class Student to our running example:

public data class Person { string FirstName; string LastName; }
public data class Student : Person { int ID; }

And let’s start our with-expression example by actually creating a Student, but storing it in a Person variable:

Person person = new Student { FirstName = "Scott", LastName = "Hunter", ID = GetNewId() };
otherPerson = person with { LastName = "Hanselman" };

At the point of that with-expression on the last line the compiler has no idea that person actually contains a Student. Yet, the new person wouldn’t be a proper copy if it wasn’t actually a Student object, complete with the same ID as the first one copied over.

C# makes this work. Records have a hidden virtual method that is entrusted with “cloning” the whole object. Every derived record type overrides this method to call the copy constructor of that type, and the copy constructor of a derived record chains to the copy constructor of the base record. A with-expression simply calls the hidden “clone” method and applies the object initializer to the result.

Value-based equality and inheritance

Similarly to the with-expression support, value-based equality also has to be “virtual”, in the sense that Students need to compare all the Student fields, even if the statically known type at the point of comparison is a base type like Person. That is easily achieved by overriding the already virtual Equals method.

However, there is an additional challenge with equality: What if you compare two different kinds of Person? We can’t really just let one of them decide which equality to apply: Equality is supposed to be symmetric, so the result should be the same regardless of which of the two objects come first. In other words, they have to agree on the equality being applied!

An example to illustrate the problem:

Person person1 = new Person { FirstName = "Scott", LastName = "Hunter" };
Person person2 = new Student { FirstName = "Scott", LastName = "Hunter", ID = GetNewId() };

Are the two objects equal to one another? person1 might think so, since person2 has all the Person things right, but person2 would beg to differ! We need to make sure that they both agree that they are different objects.

Once again, C# takes care of this for you automatically. The way it’s done is that records have a virtual protected property called EqualityContract. Every derived record overrides it, and in order to compare equal, the two objects musts have the same EqualityContract.

Top-level programs

Writing a simple program in C# requires a remarkable amount of boilerplate code:

using System;
class Program
{
    static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

This is not only overwhelming for language beginners, but clutters up the code and adds levels of indentation.

In C# 9.0 you can just choose to write your main program at the top level instead:

using System;

Console.WriteLine("Hello World!");

Any statement is allowed. The program has to occur after the usings and before any type or namespace declarations in the file, and you can only do this in one file, just as you can have only one Main method today.

If you want to return a status code you can do that. If you want to await things you can do that. And if you want to access command line arguments, args is available as a “magic” parameter.

Local functions are a form of statement and are also allowed in the top level program. It is an error to call them from anywhere outside of the top level statement section.

Improved pattern matching

Several new kinds of patterns have been added in C# 9.0. Let’s look at them in the context of this code snippet from the pattern matching tutorial:

public static decimal CalculateToll(object vehicle) =>
    vehicle switch
    {
       ...
       
        DeliveryTruck t when t.GrossWeightClass > 5000 => 10.00m + 5.00m,
        DeliveryTruck t when t.GrossWeightClass < 3000 => 10.00m - 2.00m,
        DeliveryTruck _ => 10.00m,

        _ => throw new ArgumentException("Not a known vehicle type", nameof(vehicle))
    };

Simple type patterns

Currently, a type pattern needs to declare an identifier when the type matches – even if that identifier is a discard _, as in DeliveryTruck _ above. But now you can just write the type:

DeliveryTruck => 10.00m,

Relational patterns

C# 9.0 introduces patterns corresponding to the relational operators <, <= and so on. So you can now write the DeliveryTruck part of the above pattern as a nested switch expression:

DeliveryTruck t when t.GrossWeightClass switch
{
    > 5000 => 10.00m + 5.00m,
    < 3000 => 10.00m - 2.00m,
    _ => 10.00m,
},

Here > 5000 and < 3000 are relational patterns.

Logical patterns

Finally you can combine patterns with logical operators and, or and not, spelled out as words to avoid confusion with the operators used in expressions. For instance, the cases of the nested switch above could be put into ascending order like this:

DeliveryTruck t when t.GrossWeightClass switch
{
    < 3000 => 10.00m - 2.00m,
    >= 3000 and <= 5000 => 10.00m,
    > 5000 => 10.00m + 5.00m,
},

The middle case there uses and to combine two relational patterns and form a pattern representing an interval.

A common use of the not pattern will be applying it to the null constant pattern, as in not null. For instance we can split the handling of unknown cases depending on whether they are null:

not null => throw new ArgumentException($"Not a known vehicle type: {vehicle}", nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))

Also not is going to be convenient in if-conditions containing is-expressions where, instead of unwieldy double parentheses:

if (!(e is Customer)) { ... }

You can just say

if (e is not Customer) { ... }

Improved target typing

“Target typing” is a term we use for when an expression gets its type from the context of where it’s being used. For instance null and lambda expressions are always target typed.

In C# 9.0 some expressions that weren’t previously target typed become able to be guided by their context.

Target-typed new expressions

new expressions in C# have always required a type to be specified (except for implicitly typed array expressions). Now you can leave out the type if there’s a clear type that the expressions is being assigned to.

Point p = new (3, 5);

Target typed ?? and ?:

Sometimes conditional ?? and ?: expressions don’t have an obvious shared type between the branches. Such cases fail today, but C# 9.0 will allow them if there’s a target type that both branches convert to:

Person person = student ?? customer; // Shared base type
int? result = b ? 0 : null; // nullable value type

Covariant returns

It’s sometimes useful to express that a method override in a derived class has a more specific return type than the declaration in the base type. C# 9.0 allows that:

abstract class Animal
{
    public abstract Food GetFood();
    ...
}
class Tiger : Animal
{
    public override Meat GetFood() => ...;
}

And much more…

The best place to check out the full set of upcoming features for C# 9.0 and follow their completion is the Language Feature Status on the Roslyn (C#/VB Compiler) GitHub repo.

Happy Hacking!

277 comments

Comments are closed.

  • Avatar
    Gary Des Roches

    Covariant return types are a very cool feature! Are they supported by the runtime, or is this syntax sugar? What will other languages see when they reference an assembly with them?

    • Mads Torgersen
      Mads TorgersenMicrosoft logo

      They will be supported in the runtime. There is a way to do them with compiler tricks, but that results in brittle binary code so we’ve stayed away from that. Other languages may have to do a bit of work to deal with it when they see it in an assembly.

      • Avatar
        Gary Des Roches

        Excellent! Can’t wait to use them! Will there be any new metadata or MSIL needed? In other words, will Mono.Cecil need to do anything to support it? I’m working on a .NET compiler for fun, and I’d love to implement the feature as well.

  • Avatar
    John King

    data keyword is here, but it just not the thing I want 🙁
    the record is just a anonymous class that give a name.
    I want a dictionary type that have intellisence support like this:

    “`C#
    class SomeOptions : Dictionary
    {
    public string StrOption1
    {
    get=>this.ContainsKey(“StrOption1”) ? this[“StrOption1”] : default;
    set =>this[“StrOption1”] = value;
    }

     public bool BoolOption2
     {
    get=&gt; this.ContainsKey("BoolOption2") ? this["BoolOption2"] : default;
    set =&gt; this["BoolOption2"] = value;
    }
    

    }
    “`
    and this can be use in many scenario!!!!

    • Daniel Kaschel
      Daniel Kaschel

      It sounds like you want a dictionary where the values have different types. Like, Dictionary<T, ?>. There is, of course, Dictionary<T, object>, but these wouldn’t provide intellisense.

      What you want is impossible. Intellisense works because it can accurately predict the type of each object and expression. In the dictionary you want, it is impossible to predict the type of a lookup value.

      The closest to what you want might be Dictionary<T, dynamic> which won’t offer intellisense but will allow you to directly call properties of the returned object. But personally, I avoid dynamic whenever possible.

      • Avatar
        John King

        @MadsTorgeren @DanielKaschel
        what I want is a dynamicType with “shape-like” class interface.
        for example (previose I want it use data keyword, but now because it already taken, I’ll use dynamic keworkds)
        “`C#

        public dynamic class SharedForManyExtendedOptions{
        string KnownPropertyA;
        bool KnownPropertyB;
        }

        var option = new SharedForManayExtendedOptions();
        foreache(var plugin in Service.GetService<IEnumberable>)
        {
        plugin.AddOrChangeOrUseOptions(option); // this is where things you do not know when compile, example : plugin system
        };
        //where the framework know
        if(option.KnowPropertyB){
        // do something with
        }
        // where the framework do not know
        option[“unknownNumber”] = 1;
        option[“unknownNumber”] += 1;
        foreach(var key in options.GetKeys())
        {
        Console.WriteLine($”{key}:{option[key].ToString()}”) // do not need reflection

        }

        “`

        there are a few area in C# is not good than js like dynamic language
        1. extend object
        2. reflect is slow and is not preferred
        3. AOP is much easier in js, and in C# it has limit and not easy (emit, virtual method || interface)
        4. custom clone : js var x = { …y, a =1,b=2 }
        there is propose that called “shape” , but I think maybe it can be just called “pattern” a compiler time tech
        if a class A can be ensure match the pattern PA , then CLR skip the runtime check to direct call it’s member (reflect call with out type check)

        class A
        {
           public string A{ge;set;}
           public bool  B{get;set;}
           public void Add(int a, int b){...}
           public void Delete();
        }
        pattern PA
        {
          string A{ge;set;}
           bool  B{get;set;}
           void Add(int a, int b)
        }
        
        ///  use
        
        Func  method =  a=>{
          console.WriteLine(a.A) // skip the type check , because we know a.A is String or Child type of String
        }
        
        method(new A())  // compiler know A is match Pattern PA
        
        object foo = new A();
        
        if(foo is PA a){ // this is called  pattern detect, it happened when running , is is may a little bit slow , but when use the  a variable will be fast
         console.WriteLine(a.A)
        
        }
    • Mads Torgersen
      Mads TorgersenMicrosoft logo

      Function pointers is one of those features where we hold our noses a little bit for a greater cause! 😀

      Function pointers (not delegates, but low-level, actual function pointers) are fully supported in the runtime, but they have never been surfaced in (unsafe) C#. We’re doing that now for the sake of interop scenarios with native code. We really do not want everyone to start using them! But having them in the language will make low-level C# framework code able to do safer and more efficient interop, benefitting everyone on top.

  • Avatar
    Aaron Franke

    Can we please get support for global using statements? I would like to implicitly have all scripts in my C# project use using System; and potentially other things like controlling precision by having an alias like this: using real_t = System.Single; etc (swapping out with System.Double for situations where doubles are needed), but right now I have to repeat this using definition in every single file where I want the alias to exist in.

    By the way, I absolutely love target-typed new!

  • Rasmus Schultz
    Rasmus Schultz

    Top level programs: this is just needless complexity and a bad idea.

    It’s “overwhelming for language beginners”? 🤨

    This will add learning curve, not remove it – where does args come from, where does my return value go, why can’t I write statements like this in other files, why can’t I access a variable in the main file, how do I write a method, etc. – you’ve added a veil of complexity and learning curve, for what?

    Less indentation – in one file of your app?

    So they can put off learning what class, static and void keywords mean – for about five minutes?

    Bad call, guys.

    • Avatar
      Gary Fletcher

      After seeing the Top-Level Programs feature I was so disturbed that I actually created an account just to express by concern even though Rasmus Schultz pretty much summarized exactly what I wanted to write.

      I echo all his comments and want to express what a terrible idea it is to screw this language up just so you can have a short and pretty “Hello World” sample in chapter one of every future C# book or learning C# blog article.

      I believe you have failed to make the case why this feature is needed. Please reconsider it.

      • Daniel Kaschel
        Daniel Kaschel

        The feature is totally optional, so don’t fret that it has “ruined the language”.

        As someone who has been teaching a complete novice to program 100% from scratch, I really appreciate this feature. It would have allowed me to delay the complexity of discussing namespaces, classes, accessibility, and wtf “void” is for many lessons rather than needing to tackle them on day 1.

        • Avatar
          Gary Fletcher

          Respectfully, if you are teaching someone C# and you do not get into discussing namespaces, classes, and accessibility on day one I cannot imagine that anyone is going to be getting much out of that class (or day one is REALLY short).

          If a feature is added to a language it should be to solve a genuine problem or shortcoming of the language. This does not do that.

          Top level programs are an inferior alternative to a static Main which is a good teaching mechanism because not only does it introduce the class and namespace system immediately it helps connect the dots for new C# developers on how their application is invoked by the operating system and how they can get information from that invocation and return information as well (and it does so without magic variables). Not to mention attribute decorations for things like STA/MTA which are very common.

          It is not too late to pull this feature. There are plenty of positive changes in C# 9.0 to be proud of – dump this one.

    • Avatar
      MgSam

      I wish Mads would stop claiming this feature is for “language beginners” when clearly C# has thrived over the past 20 years without top-level statements.

      The team on Github said the real reason for this feature is Jupyter Notebooks. And there is obviously a Microsoft-wide initiative to do anything possible to support them so this feature was probably dictated by the brass from on high.

    • Mads Torgersen
      Mads TorgersenMicrosoft logo

      We truly believe that top level statements make C# more approachable and programs easier to read.

      The C# scripting syntax has allowed top-level statements for many years, and we have good experiences with that. The scripting syntax is what is being used in Jupyter Notebooks for instance. We also get frequent feedback from people teaching C# to novice programmers that there’s too much fluff to even get to “Hello World”. So the feature serves these purposes:

      1. Make C# programs simpler and easier to read and write
      2. Make C# more approachable for learners
      3. Reduce distance between “normal” (project) C# and scripting syntax

      Not all new features speak to everyone. You are welcome not to use them.

      • Vincent Thorn
        Vincent Thorn
        1. C# is not for “complete dumb novices”. Obviously you need some OOP experience before you deep into all those classes/inheritance/methods/etc.
        2. .NET runtime do not need any classes, you can run any code. So why not just make plain “modules” where you can mix flat code with class declarations? (I mean w/o any hidden wrappers like “void main()”)

        As usual, MS takes great idea and produce scanty implementation. Then (after years) they implement it as it had to be, but it’s too late – “compatibility” and so on. Why MS has so narrow vision?? Thousands developers SEE the problem, but not MS! Seems your team is not so great and better they do Notepad-2 than design world-wide language.

      • Avatar
        Robert McCarter

        Wow – when I first read this I was initially really excited – I thought perhaps you where going to take another F# idea, and my top-level functions would be converted into public static methods on an automatically created static class. That would make C# wonderful and feel much more functional – especially when combined with the using static directive.

        So while others are saying it’s not necessary, I wish you’d gone even further – especially with the addition of the wonderfully functional records.

        Thanks @Mads Torgersen! 🙂

      • Avatar
        Chris Christodoulou

        “You are welcome not to use them.” That’s exactly what I’ve done. 15 years of C# and i’ve moved on to ES6 and node. It’s just not evolving fast enough and in the directions it needs to go.

        I was excited for 2 seconds until I saw how crippled this is. The first time I touched C# back in 2005 I was literally astounded that an implicit global namespace wasn’t a thing. An inductive proof where the base case isn’t allowed. Then we slowly see pacifying yet hobbled features like static imports and now whatever this is. We have powershell with a different and ugly syntax because C# can’t be coded like a scripting language or easily extended with DSLs. No REPL workflow to speak of. I can’t import regular source files into something like ScriptCS without modification. Meanwhile ES6+ / Node.js / React (i’m looking at you XAML) are outrunning the .NET stack because it can actually evolve according to users’ needs instead of the glacial spoon-feeding we receive when the Microsoft language gods deign to give us plebs a taste of category theoretic symmetry. Too many arbitrary stifling compiler rules. Very tiring.

        • Avatar
          saint4eva

          To you C# is not evolving? Yet asyn/await has found itself in javascript. Linq/ lambda is now in javascript. And reactive extension (RX) is now in every language. C#/ .NET introduced those.

          Language evolution is different from different web framework coming out every 2 days – some cannot even last for one week. You have abandoned C# / .NET yet you are here commenting – very funny.

          • Avatar
            Chris Christodoulou

            “To you C# is not evolving”
            Not really. I said: “It’s just not evolving fast enough and in the directions it needs to go.”

            “Yet async/await has found itself in javascript. Linq/ lambda is now in javascript. And reactive extension (RX) is now in every language. C#/ .NET introduced those.”
            Irrelevant to my point. I am not talking about what C# did right. I am talking about what they did wrong. I’ve been a software architect for a while now and I’m not just saying these things to be negative. There are mathematical violations that poison the way people think (OO has failed at many of these and it poisoned me too for a very long time) and I’m just not seeing enough abstract/meta change to continue being excited about C#.

            “You have abandoned C# / .NET yet you are here commenting – very funny.”
            Our dev shop is C# backend and ES6 frontend. I’ve been a software architect since 2011. I still do C# stuff too, but the language has become super frustrating (this is coming from somebody who thought javascript was a joke for decades). I came here because a team member was singing praises of new C# features. I got excited thinking the toplevel scripting was at all useful, then for the umpteenth time in my professional life I was disappointed.

            I am talking about fundamental design flaws in the language that haven’t been fixed since I started using it 15 years ago.

            There are tons, but off the top of my head:

            js: var foo = ()=>”hello world”
            C#: compiler error (a bunch of excuses here about the type system yadda yadda)

            F#/JS/etc: declare functions at the toplevel
            C#: was impossible, now crippled but possible (how do i share code from these flat programs if nothing can be accessed outside the toplevel)

            These are fundamentally ridiculous things. As for the second one, imagine a piece of paper telling you you have to put your mathematical proof inside a namespace (and not a symbolic one at that like ES6 Symbols but a stupid collidable arbitrary text tag) and not just write wherever you want.

            I think my post was clear enough about the problems here. If you want more details we can discuss elsewhere as there are too many abstract violations to count that i’ve encountered over the years and I’ve admittedly been too lazy to document or complain about on uservoice or whatever. Seeing issues being discussed to death and then perfunctorily closed soured my will to contribute.

            https://kriteris.wordpress.com/2016/11/23/contact/

            JS has babel and the language can evolve overnight. Yes it has issues. I never claimed it didn’t. The anarchy is disquieting, yet somehow liberating. However, if I want to add pipe syntax to it, to make it more fluent, I get the Babel plugin and i’m off to the races. Way more like legos. I am tired of DRY violations, symmetry violations, unshareability, tedious ceremony and other abstract design flaws that clash with math and category theory and bias people’s brains into thinking in weird and convoluted ways. There is a certain symmetry and beauty to math, and every time C# tries to fix one of those flaws, it fails miserably. One line of code in F# generates a crapton of boilerplate under the hood. The same goes for advanced ES6 syntax that is transpiled into a ton of code. C# has T4 text templates but no way for hygienic macros (which is basically what Babel provides), so it suffers in an abstract sense. Opinionated power-reducing design decisions and totalitarian ones at that. Any such internal tools Microsoft has are hidden and not first class citizens of the stack (my impression). One of the most beautiful things about C# is indeed LINQ/Rx as you said, because it’s the simplest and most FP pattern in the language. Extension methods are one of the best ways to share code. To use an excel metaphor, data classes are tables and extension methods are formulas between those tables. Simple and easy.

            I am not trying to be mean. I am saying all this because i’ve devoted so much of my life to C#, but this freedom i’ve felt in the node ecosystem is refreshing. I want that for C#, but i’m not seeing it, even with this core/linux push and open sourcing some of the stack. I think the biggest flaw in C# is its resistance to just letting more anarchy into the system and “following the math”. Allow more power and let it evolve on its own. Process and ceremony are a form of arrogance. DRY is one of the biggest killers in OO.

            These talks explains some of what i’m trying to say. I am not opinionated about Clojure, I am talking philosophically. Design philosophy and freedom are very important in having languages evolve the way nature intended. They need to go where Rich Hickey tried to go (I am not here to debate language choices as I don’t really care as long as coding feels as good as playing legos).

            https://www.infoq.com/presentations/Simple-Made-Easy/
            https://www.youtube.com/watch?v=2V1FtfBDsLU&feature=youtu.be&t=2109

            I heard a quote once that javascript got all the hard things right and all the easy things wrong.

            I want C# to get more of the hard things right.

            Apologies as I intend no offense. I try to be clinical and detached. I don’t really have an ego. I am only interested in truth.

      • Сергей Игнатенко
        Сергей Игнатенко
        • “Not all new features speak to everyone. You are welcome not to use them”

        On individual level – yes, but have we any direct method to prevent usage of these optional features on project level? Some compiler arguments (like –languageFeatureConstraints [*]) will be nice to have, then.

      • Avatar
        Andrew Buchanan

        II don’t think I’d personally use this unless I can run it interpreted like bash or PowerShell. If it compiles behind the scenes or whatever that’s ok.
        Then I might use it in place of bash, PowerShell, and python for scripting tasks. ./myapp.cs

        I personally think you could go further though. Why limit it to just one file?

        As other people mentioned, asp.net has a global usings for views and something like that could also help. Why don’t you automatically include using system or infer the usings when no conflict exists? It would be a one liner for hello world and save a lot of boilerplate in general for model classes, attributed enums, etc. Usings would be required to resolve namespace conflicts only. Intellisense basically does all that nowadays right down to telling you which nugets aren’t installed sometimes.

        Namespaces and could be inferred from project folder layout if not specified. Class names from filenames if not specified. I think most people name and organize that way anyways.

      • Avatar
        Chris Schaller

        This will be a great help for C# applications that utilise a mix of scripting, code generation and traditional code. It’s not intended to replace how your traditional application is authored, its to inspire a host of new design and implementation paradigms within c# instead of using different languages to handle scripting or generation elements because they are easier to implement into your application domain.

        That’s really it, without this top level support it is common to resort to using other languages for script support, so you can focus on the specific logic within the script and not have to worry about getting all the boilerplate exactly compiler compliant. This is especially relevant in applications where power users can manipulate scripts at runtime, they should be lightweight, the code should be easy to visually debug, all these language enhancements help us in this respect, reduce the amount of code = reduce the points of error.

    • Avatar
      Alex Clark

      Agree 100%. Everything else looks fantastic, but this seems like an unnecessary complication to accommodate people who will momentarily be in the absolute novice stage of learning the language. It saves a whopping two lines of code and some braces, but involves “magic” parameters and “it just works” compiler trickery.

      It’s not about “if you don’t use it, don’t like it” it’s about it getting abused in a codebase that someone (i.e. me) may end up having to support. It doesn’t add anything positive to the language but definitely does violate the principle of least surprise. Unless there’s some long-term “we have an amazing scripting idea for this feature” strategy, I really don’t like the idea of it making its way into the language.

      The rest is golden though. Record types looks like a TON of work has gone into them and I can’t wait to start using them to severely clean up some noisy code, along with init properties.

    • Yogesh Jagota
      Yogesh Jagota

      I agree too. A single namespace statement at the top, just like the new using statements in functions, without the need for braces and indentation would have been a better idea than this.

    • swapnil dhondkar
      swapnil dhondkar

      Totally agreed.

      New features are always welcome but they should not keep on adding again and again new stuff which now days no one is using and irrelevant.

      Absolutely bad call.

  • Avatar
    Davide Curioni

    Sad part of the story, I cannot even use C# 8.0 in my huge classic ASP.NET application. I don’t see why you should not support that platform anymore with the new versions of the language. 🙁

    • Mads Torgersen
      Mads TorgersenMicrosoft logo

      We need to add new library and runtime features to support new language features. Since we are not evolving the classic desktop Framework, the language cannot fully target it. If you avoid using some of the new language features you may get things to work, but your mileage may vary: it is not a supported scenario.

    • Vincent Thorn
      Vincent Thorn

      Actually they have nothing “great” in 8.0 – you can live w/o it. 🙂 (like we live last 18 years) But if you really need it, you can write it! What is ASP? Just “inverted” C#! Instead of writing Console.Write(“some HTML code”) on C#, you write “some HTML code < C# island > again HTML ” on ASP. Write “inverter” and viola!