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!

215 comments

Leave a comment

    • Avatar
      Andy GockeMicrosoft logo

      Hey Steve, the current plan is that C# 9 will only be officially supported on .NET 5. However, in the current design records do not depend on any new CLR features, so many things may work in .NET Standard 2.0 in an unsupported fashion (similar to C# 8).

  • Avatar
    Michael Taylor

    Please provide links for each of these new features to the appropriate place in Github to provide feedback on them. In some cases (e.g. records) the issue is available by going to the list of new features. But other things (e.g. init only properties) don’t seem to have an entry in the list and searching returns too many results.

  • Avatar
    MgSam

    Mads, you are burying the lead for target-typed new expressions. I was originally opposed to this feature because it doesn’t at first glance seem to add any new expressiveness, but it is fantastic for declaring and initializing collections. That is what you guys should be showing off.

    var a = new[] 
    {
        new Foo(1, 2, 3),
        new Foo(1, 2, 3),
        new Foo(1, 2, 3)
    }

    vs

    var a = new Foo[] 
    {
        new (1, 2, 3),
        new (1, 2, 3),
        new (1, 2, 3)
    }
    • Mads Torgersen
      Mads TorgersenMicrosoft logo

      It’s as you’d expect, in the sense that there are no hidden or unexpected costs. Supporting non-destructive mutation means copying the object field by field. Value-based equality means comparing the object field by field, sometimes recursively if the fields also contain records with value-based equality. It’s good to be aware of these costs as part of your decision to use records.

      • Hannes Kochniß
        Hannes Kochniß

        Ok, so the JIT doesn’t understand records yet (as it once didn’t care about sealed classes or prob. still doesn’t understand the Index class), but might there be a future where the immutability of records is taken advantage of in the runtime? Thinking similar to readonly static fields/sealed class/readonly structs etc.

    • Avatar
      Cédric POTEE

      I suppose it’s because making the word “record” become a keyword would prevent some legitimately named “record” variables.
      Whereas preventing naming a variable “data” is much more acceptable.
      Just my guess.

      • Avatar
        Jonathan LEI

        Definitely not the reason. They can make it a contextual keyword such that it’s not made reserved. This is exactly what they’re doing to data.

        Also, how would data be used less as identifier compared to record anyways.

    • Avatar
      Andrey Kontorovich

      I think the reason is just simplicity. When you see “data class” you undrestand, that it’s “class” with specific semantics for some cases. But it sill behaves like class in terms of memory location / generics / etc. With word “record” you have to mention somewhere else that it’s still class, but init only and with overridden equality members.

    • Mads Torgersen
      Mads TorgersenMicrosoft logo

      The current plan is to use “record” as the keyword, but our prototype still says “data” based on earlier proposals. We are still considering whether you should say “record class” or just “record”. It hinges on whether you also need struct records to be a thing in the language. We suspect we might get away with making all structs records – after all they already have field based copying (on assignment), so we could trivially implement with-expressions for them. And they also have value-based equality. If there’s no need to ever say “struct record” then “record” could always just mean “record class”.

      • Avatar
        Christian Brevik

        Since the behavior of Records is so distinct from normal classes, it seems like a good idea to drop the “class” keyword? Value-based equality, the default access modifier is public, etc.

        My first reaction was that it seems like a potential footgun to use “class”.

  • navnath kale
    navnath kale

    Excellent !!! I have been craving for Covariant returns feature. Finally i can tidy up some of my old code. Rest of goodiness is okay not really stopping dev from design decisions.

    How about generic type inference at class level #3465 ? When can we expect that ?

    • Avatar
      MgSam

      They are separately thinking about some kind of “validator” feature that would allow you to define validation logic for a class that would run after init-only properties are assigned. I doubt it’ll be in the C# 9.0 release though.

        • Mark Murphy
          Mark Murphy

          But for required init-only properties don’t we want the ability to use non-nullable types for our properties….? Don’t we need some way to tell the compiler “this property must be included in the object initializer so it doesn’t need a default value”? Otherwise aren’t we essentially forced to make e.g. all required string properties nullable…? I’m not sure value checks in the “validator” will solve this issue….

          Edit: please also note we may want a value type to be a required init-only properties too as its default value might be meaningless….

          • Avatar
            Константин Салтук

            Actually it should.
            For example, Resharper introduced nullability long time ago, using [NotNull], [CanBeNull], [ItemNotNull], etc.
            And it will prevent you from creating instance of class with [NotNull] field without initializing this field with corresponding warning.
            I think standard compiler nullability checking will warn you in case of creating record type with not nullable property without initializing it.

          • Avatar
            Stilgar Naib

            I really hope the current implementation of records will indeed produce nullability warning if non-nullable properties are not assigned. Otherwise what was the point of the nullability feature in C# 8.0?

      • Mads Torgersen
        Mads TorgersenMicrosoft logo

        We’ve gotten mixed feedback on those – we call them “final initializers” now – where some folks are worried about the complexity of ensuring that they always get called, even in reflection/generic/dynamic scenarios. I still really like the feature and I hope we can land it.

    • Avatar
      Phil Martin

      Yes, this was the first thing that came point mind. I like properties because they are a more descriptive way of constructing, and would very much like to have the compiler enforce certain properties to be initialized on construction.

      • Mark Adamson
        Mark Adamson

        If you don’t apply the nullability check on unset properties at compile time now for record initialisers then presumably it won’t be able to be added later. It would break any code that was previously not initialising the non-nullable properties.

        I hope you are able to solve at least that case

  • Avatar
    Greg Ingram

    Good stuff and looking forward to the release!

    Regarding Records and mutation, what would this look like when data binding record objects to XAML UI? For example, if it is hooked up to a DataContext, ItemsSource, etc., how would it be updated?

    • Mads Torgersen
      Mads TorgersenMicrosoft logo

      The C# compiler itself (Roslyn) is built on an immutable object model, and that decision turned out to bring us tremendous value! Immutable data structures allow the before and after states to coexist, enabling rollback, diffing etc. There’s no shared mutable state so you can go to town with concurrency. And you can safely expose data structures directly to external consumers without worrying that they might mutate them. We believe that this is a common experience, witnessed by the increased popularity of non-mutation-based APIs and framework, e.g. in the UI space.

      One thing that has been really painful with the Roslyn experience was the amount of boilerplate code (in the form of “With” methods) it took to support non-destructive mutation. That is the motivation behind the “with” expressions in C# records.

      Another concern with large immutable data structures is the garbage collection and copying overhead of creating new objects all the time. This is a constant source of work in Roslyn, and we have a massive regression testing infrastructure to keep an eye on it. This is more of an inherent problem with the programming style, at least when applied in the very large, and C# records do not provide any groundbreaking relief from that. When programming in this style you will be creating more objects, and you need to keep an eye on the impact to performance.

      • Szilárd Ádám
        Szilárd Ádám

        For copying large objects as part of keeping up immutability/non-destructive mutation I suggest you have a look at how (IIRC it’s these languages – I haven’t worked with them for a while) Erlang and Elixir does it: if I make a copy of something, the references to the values remain the same in the original and the copy – actual copying is only involved when I modify some value. Like this pesudo code below:

        // first name stored at memory location 634, last name at 859, and address at 8856 in p1
        Person p1 = { FirstName=“Josh“, LastName=“Smith“, Address=“somestr. 33.“ };
        // FirstName, LastName, Address still stored at memory locations 634, 859, and 8856, respectively in p2 – they share references until a property of p1 or p2 is changed
        Person p2 = copyof(p1);
        // last name in p2 stored at memory location 4877 now, everything else remains shared between the records
        p2.LastName = “Fisher“;

        You probably know this better than me, but I believe strings already work similarly in C#, but this could be implemented for every type in the case of records if copy performance is important. The downside of this approach is that you need to keep track of which properties are on automatically shared memory locations, which takes up some extra space.

  • Avatar
    Farhad Gholami

    I think the mentioned part, breaks single responsibility of “private” keyword and also frightens newcomers learning OOP or coding in C#. Still everything else was great. Thank you.

    “””

    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;

    “””

    • Mads Torgersen
      Mads TorgersenMicrosoft logo

      To be honest we’ve been debating this a lot. None of the options are great. We could not have a shorthand, but then it’s longer. We could have an explicit keyword, but then it’s still longer, and potentially confusing too. This is what most of us currently believe to be the best design point, but we are very open to feedback – and to fresh ideas!

      • Avatar
        Osvaldo Saez Lombira

        It’s great that you guys are still open to feedback on this because that syntax goes against the intuitive nature of the language, which IMO is what makes C# so great.
        Moreover, we don’t get that much from it. I mean, it is just a couple of lines less per record declaration. I think ultimately, we would be losing way more than what we are getting.
        That said, this is a great set of new features, so thanks a lot for the hard work!

        • Hannes Kochniß
          Hannes Kochniß

          “it is just a couple of lines less per record declaration”

          I disagree. This feature very much is inline with lambdas, expression bodied members, switch expressions, and other features that just enable some originally 3-or-more-liners to be one-liners that were unnecessary boilerplate before. I, for one, welcome simplification in the language very much, it is needed for long-term-survival in an ever-growing language space.

      • Avatar
        Randy Buchholz

        Love that Records are coming, and wanted to throw in my 2c. The basis of this is correlating names with features. As I understand it there are two names being considered data class and record. If you go with data class I really don’t have much input – go with what works. But if you choose record you are stepping into a more constrained area – the term “record” has specific “legal/business” meaning. I’m putting this under “scenario” because the general scenario is showing intent. A “Record” should be absolutely immutable and have required fields – if I make a “legal record” it must be complete and unchangeable. With that in mind, I want to be able to constrain around that. I may have a records management system with a working area (data class would work even here it seems) and a “records” area. Having a “record” constraint would help enforce partitioning across these architecture areas.

    • Avatar
      Jonas N

      I read it as records are still class/reference types unlike structs that are value types.

      But yes, it can be confusing since records have auto-generated Equals() so that even two different instances have equality as soon as all properties share values.

      Why records if we have structs? Well sometimes you want the reference type rather than value types as well as the immutability.

  • Avatar
    Stilgar Naib

    I can’t think of reason to ever use init. I think all the cases of init in my codebase are covered by records

    Also if I declare a record property as non nullable like this public data class Person(string Name) will people who try to initialize as new Person { } get a warning for not assigning a non nullable property?

    • Avatar
      cheong00

      If you don’t really what to declare the whole class immutable, but just some members that shouldn’t be changed once the instance is instantiated, you’ll want to use that.

      Although traditionally we’d have put these variable as parameter in constructor and expose the read-only property later, this somehow get us out of the situation that we do something like this:

      var result = from t in context.SomeTable
      select new MyClass(t.Value1, t.Value2) { V3 = t.Value3, V4 = t.Value4 };
        • Avatar
          Avner Shahar-Kashtan

          I think a relatively common pattern I’ve used before is for a mutable class whose ID is set at construction, but the rest of the fields can be set later. Let’s say you click “Add New Item” in your data entry UI – a new record is added with a new, fixed, unchangeable GUID as ID, but the user can then edit the rest of the fields.
          A common alternative is to leave the ID blank and only generate it once the user clicks Submit, but that has other complications.

      • Avatar
        Stilgar Naib

        Allow me to ask again about how records work with nullability

        data class Person(string Name)
        
        var person = new Person { }

        Will there be a nullability warning? If yes, where? What if I declare the record without a primary constructor? I tried to test with sharplab.io but I cannot find a way to turn on nullable reference types there. I think it is very important that records handle nullable reference types properly (i.e. do not generate warnings on declaration and do not require = null! on every non-nullable property but require that non-nullable properties are assigned)

  • Anthyme Caillard
    Anthyme Caillard

    Record types … A so simple but so useful feature! But …

    Initially announced for C# 6 but when C# 6 was outs it was delayed to C#7, when C# 7 was out (1 year later) it was delayed to C# 8, when C# 8 was out (3 years later) it was delayed to C# 8.x and finally, now, it’s delayed to … C# 9!
    With no release date at all! (Except if you consider the github date “January 01, 2090” as a release date)
    For something existing in other languages since decades!

    It’s certainly the most worse disappointing experience in my developer life

      • 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
      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.

    • 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!

  • Avatar
    Bill Seddon

    These seem useful improvements. However, they represent easier ways to accomplish tasks done today perhaps using more code. Will you be offering guidance concerning performance? That is, when letting developers know when the use of a syntax style will be more or less performant than implementing the same functionality in more verbose C# code.

  • Avatar
    Adrian-Victor Greu

    Cool new features! Thanks for keeping C# young!

    Will we finally be able to initialize known classes/records like this?

    var viewModel = new PersonViewModel()
    {
        person.FirstName,
        person.LastName,
        FullName = person.FirstName + " " + person.LastName,
    };
    

    Just like initializing anonymous classes.

  • Avatar
    Richard Misiak-Kelly

    Will “Target-typed new expressions” work for fields?
    e.g. rather than having to write

        private static Dictionary<string, Func<string, string, bool>> Handlers = new Dictionary<string, Func<string, string, bool>> { ... }
      

    you could instead write

        private static Dictionary<string, Func<string, string, bool>> Handlers = new { ... }
      
  • Avatar
    Juan Fran Blanco

    Any thoughts how will this work with Deserialisation using reflection? For example Json, Database Views (ValueObjects), Blockchain, etc. Normally you will discover the properties using their attributes, could the init work as a “set one time only”.

    • Mads Torgersen
      Mads TorgersenMicrosoft logo

      One of the purposes with top-level statements is indeed convergence with script syntax (.csx). With this, the language-level syntax is pretty much the same. Scripting still has a set of directives that only make sense in scripting scenarios, and I don’t know that we can converge those.

  • Peter Morris
    Peter Morris

    public data class Child { string Name };
    public data class Parent { string Name, Child[] Children };

    Presumably Parent.Children is mutable, so I’d have to change the type to IReadonlyCollection, right?

    It would be so nice if there were a way to declare an array as immutable.

  • Avatar
    Ruslan S

    Love all of it and can’t wait to have it in my life 😀 These are the features I’ve wanted C# to have on many occasions. Especially the immutable records features, and yes, even the top-level programs. That last one can actually be MORE confusing at times, but I think it plays nicely with scripting and jupyter notebooks, which are big real world use cases for C#! We gotta accept that C# is (and needs to be) more than just a niche backend language, which it maybe was at times of .NET 1.0. You can do so many things with it now! Most of all I love the fact that C# is evolving and is staying at the forefront of programming language design, putting many languages to shame on so many levels. Please keep it up guys!!

  • Shimmy Weitzhandler
    Shimmy Weitzhandler

    WOW! That is beyond awesome!

    Please also make C# events more accessible, right now you can only refer to an event with the +=/-= operators when outside containing class.
    System.Reactive is a great way to enable LINQ over events and tasks, but because C# has a block over events, converting an event to an observable is super tedious:

    var fsw = new FileSystemWatcher(@"C:\Users\Public", ".");
    fsw.EnableRaisingEvents = true;
    
    var fswCreated = Observable.FromEventPattern(fsw, nameof(FileSystemWatcher.Created)) //or other tedious overloads
        .Where(...)
        .Select(...);
    
    fswCreated.Subscribe(pattern => Console.WriteLine("{0} was created in {1}.", pattern.EventArgs.Name, ((FileSystemWatcher)pattern.Sender).Path));

    Where it could have been something like:

    var fsw = new FileSystemWatcher(@"C:\Users\Public", "*.*");
    fsw.EnableRaisingEvents = true;
    
    var fswCreated = fsw.Created
      .ToObservable()
      .Where(...)
      .Select(...);
    
    fswCreated.Subscribe(pattern => Console.WriteLine("{0} was created in {1}.", pattern.EventArgs.Name, ((FileSystemWatcher)pattern.Sender).Path));

    Please see my GH suggestion.

  • Avatar
    André Köhler

    I really like the direction C# is going in.
    I hope the “with” keyword will also work with the “readonly struct”s introduced in C# 7.2. Structs usually do not have as many properties as classes do (because all properties/fields have to be copied on assignment), but I guess it would be nice if we could easily convert between a “readonly struct” and “data class” without having to rewrite too much existing code that uses them.

  • James Crann
    James Crann

    I am a little confused with Logical Patterns using ‘AND’ and ‘OR’, I can’t see how it makes it any clearer? There are now two different ways of writing expressions depending on whether they are in our out of a switch statement?

    >= 3000 and <= 5000 => 10.00m
    

    vs

    >= 3000 && <= 5000 => 10.00m
    
    • Selman Genç
      Selman Genç

      Almost none of these stupid features make anything clearer… they are turning c# into c++ and worse. I guess companies that use C# in production will need to create guidelines that will ban the usage of some features as they do for C++.

    • Avatar
      Richard Gibson

      It depends on whether you’re writing a normal boolean expression or a pattern test. Boolean values are combined with &&, patterns are combined with and.

      if (x > 10 && x < 100) ...
      

      Can also be written as:

      if (x is > 10 and < 100) ...
      

      Which is a feature people have been asking for pretty much since C# was invented.

      • Avatar
        Ian Marteens

        People has also asked C# to drop braces since C# was invented. But then you had Pascal for that crowd.

        Just tell me: which of the two “equivalent” ways is more efficiently compiled? Or think about this:

        if (x != null)
        versus
        if (x is not null)

        Pretty easy to forget about one of them when compiling or jitting. It has already happened.

  • Avatar
    Pados Károly

    I like many of the features that are coming (init-only properties, with-expressions, improved patter matching, covariant returns …), but I’m not so sure about the current implementation of records, even if the concept is ok. Basically, it makes things confusing.

    I mean, until today, we had structs for value-types and classes for reference-types. Now we have records that are reference types but should behave like values types, and they are called not records but data classes but they have different defaults than classes. Rly? In my eyes one of the most important aspects of any good computer language is consistency. Hence my suggestions:

    1. Keep the naming consistent. Use either “data classes” or “records”, and whichever you decide upon, use that name both for the concept and in the language syntax.

    2. Should you decide to keep the name “data class”, then commit to it being a class. Don’t change defaults like accessibility (public vs. private) unless necessary for the basic concept to work.

    3. If you want a non-mutable type, then make it a first-class citizen and enforce the rules. Don’t go half-way like “records should be non-mutable but you can make them mutable and in that case they will break containers”. After all, then there is not much difference to classes except for the value-based equality, which tbh you can get even today easily by overriding a couple of methods. I feel using records (if that’s what you’ll call them) should express a strong intent by the user to keep things immutable, and in this case, the compiler should enforce it. If they want mutability, people can use classes.

  • Selman Genç
    Selman Genç

    By adding so many unnecessary features and so many syntactic sugars that no one needs, you are making the language harder to learn and harder to read every day.

    For example, about “Top-level programs” which is supposed to make hello world programs easier to understand for beginners, it’s doing the opposite. It’s hiding the important details that there is a class and a main method there, one could simply think you can write expressions outside of class and method context, but it’s not true. Although the intention is good, I think it won’t accomplish what it’s supposed to.

    In addition to that those Relational patterns and logical patterns syntax makes me feel nauseous.

  • Avatar
    Stefano Tempesta

    If init accessors can only be called during initialization, and they are allowed to mutate readonly fields, are they basically readonly set?

    I mean, init is just a shorter version for
    public int Age { get; readonly set; }

    • Avatar
      Ian Marteens

      The “init” thingie is a redundant way to write things like this:

      var p = new Person(firstName: “Wile”, lastName: “Coyote”) { MiddleName = “E.” };

      If you really need some required properties, ensure you pass them in the constructor. Then, you’ll have a way to check class invariants, if you need to. We already had named parameters and default values for parameters. Now, it’s pretty hard to enforce a class invariant.

  • Avatar
    Степанов Андрей

    Guys, do we really need all that syntax-sugared complexity in C#? I learned C# many years ago because it was simple and powerful. Now I have 15 years of C# experience and I can hardly remember all those new syntax constructions. I think I will never really use most of them. Poor C# junior programmers, they will have to learn all this stuff… In a perfect world where C# is the only language to rule-them-all it would be nice to have features from all languages built into one C#. But in a real world programmer have to learn so damn much languages, frameworks, libraries, that each new feature makes much more stress than happiness.

  • Avatar
    Diego Mendes .

    The init feature, how will it handle for example a property that can be initiated but not changed outside a class(private set)?

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

    Wouldn’t be simpler if it were implicit, so init is not required?
    Alternatively init could be used to define required initialisation.

    On the data record, if I have a class that has some properties which are required and other optional, like below:

    public data class Person(string FirstName, string LastName)
    {
    string FirstName; 
    string LastName; 
    string MiddleName; //optional
    }
    

    why can’t the compiler accept this:

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

    And implicitly set first and last name in the constructor

  • Vincent Thorn
    Vincent Thorn

    Seems I found why C# “evolves” by so ridiculous, questionable features – Mads wants smart developers … stay on C# 8.0 and on Windows7! :))
    When your language is screwed by funny team of … let’s say “foreign dancers”, it’s time for you to stop and don’t accept anymore any of their “products” until these dancers return back to India. OK, message received, WE WAIT!

  • Avatar
    Pados Károly

    Feature request that I’ve been longing for for a long time: Please add support for static variables in methods (like in C/C++, those are variables that are scoped to the method where they are defined in but they don’t forget their value between method calls).

  • Bot Dev
    Bot Dev

    IMPORTANT: Mads, could you add Min/Max Heap collection in .net like other languages have?
    These days companies are hiring people by testing their coding / algorythmic / problem solving / data structures skills on platforms like leetcode, hackerrank, codility, codesignal etc. Some problems require Heap data structure and .net developers are at disadvantage there. If one uses existing collections instead of Heap where Heap is needed then there are bad performance results and other devs like Python’s win competitions / jobs.

  • Avatar
    Mikael Öhman

    Awesome features. Pattern matching and records are great. Next I pray for algebraic data types.

    Can I assume the records feature will interoperate flawlessly with the F# records?

    I’ve long wanted to be able to use records on an API level but haven’t done so due to their being F# specific.

  • Emmanuel Lemontzis
    Emmanuel Lemontzis

    just a typo :

    public data class Person { string FirstName; string LastName; }
    //Means exactly the same as the one we had before:
    
    public data class Person // <- data reserved work 
    {
        public string FirstName { get; init; }
        public string LastName { get; init; }
    }
  • Amit Suri
    Amit Suri

    The basis of any language is to use it to “communicate” to another human being.
    IMHO, this feature “Relational patterns” and the “Nullable types” feature introduced in C#8 are going in the wrong direction.
    We are now encouraging programmers to pass and return nulls and sprinkle it everywhere making it more unreadable.
    With Relational Patterns we are making code read like spaghetti.
    Maybe I am unable to see the end goal but both these features will make the language worse and seem very much like code smells to me.

    Readablity

  • Avatar
    Sandeep Chandra

    Hi Mads,

    Really love what you guys are doing with C# but I am getting a bit concerned with where the C# language stands today. IMO it is trying to do a bit too much and trying to be more functional, I see it becoming more like C++.

    With all the learning you guys have had with C# I think it’s time that MS came out with a new modern language which would allow you to do what you guys want without worrying about backward compatibility.

  • Avatar
    Maciej Pilichowski

    First feature sold me, I am waiting for it so long 🙂

    At first glance I found one real trouble, syntax for record:

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

    This is wrong approach IMHO. Creating exception to the rules, in the end you will create a mess. C# is already complex language, don’t make it not even complex, but muddy — my vote would be for uniformity and interpreting this as it is already interpreted — private fields.

    While we are at new features, any chance for interface of numerics? It is kind of embarrassing we cannot add two numbers in generic way.

    And maybe less important but big thing still — introducing proper Unit. And if by some magic this could be compatible with void, i.e old void-methods could be used where Unit-methods are required, this would be big.

  • Avatar
    Peter Row

    Instead of adding the data keyword to a reference type why didn’t it get added to struct which is already a value type? Surely that would have made more sense.

    Top level programs? That goes straight in the bin of never use. That is truly terrible. Some random code floating around. Looks good in the hello world example used but as soon as you do anything remotely close to reality with args or calling other methods then it quickly loses IMHO and the lack of clarity for the amount of code you save writing is simply not worth it.

    • Avatar
      Maciej Pilichowski

      There is nothing to add to struct, they are values. Think of string — this is reference type which has value semantic, for example “foo”==”foo” gives you always true, no matter if you have two instances or not. Currently you can create such types by yourself, but this feature speeds up things.

  • Avatar
    Morten Aune Lyrstad

    C# used to be such a nice, clean language. It’s what attracted me to it in the first place. It’s what made it superior to C++ for me. Sure, some of the added features are nice, but… most of it feels like language bloat, making it just harder and harder to learn for new programmers.

    It’s sad to see.

  • Avatar
    James Raden

    Great article!

    You use a term I’ve not heard before — modulo casing — when talking about parameter names, and the googles aren’t being terribly helpful.

    Does that mean the toggling of the capitalization of the first letter of a property? The modulo implies that it would go both ways, as though we’re performing an operation modulo 2 on the IsUpper() value of the first letter.

    So a property FirstName would have a corresponding parameter of firstName, while a property firstName would have a corresponding parameter of FirstName.

    Thanks!