Roslyn: C# Design Notes - catch up edition, Feb 29, 2016 (deconstruction and immutable object creation)

Created on 1 Mar 2016  ·  170Comments  ·  Source: dotnet/roslyn

C# Language Design Notes Feb 29, 2016

Catch up edition (deconstruction and immutable object creation)

Over the past couple of months various design activities took place that weren't documented in design notes. The following is a summary of the state of design regarding positional deconstruction, with-expressions and object initializers for immutable types.

Philosophy

We agree on the following design tenets:

Positional deconstruction, with-expressions and object initializers are separable features, enabled by the presence of certain API patterns on types that can be expressed manually, as well as generated by other language features such as records.

API Patterns

API patterns for a language feature facilitate two things:

  • Provide actual APIs to call _at runtime_ when the language feature is used
  • Inform the compiler _at compile time_ about how to generate code for the feature

It turns out the biggest design challenges are around the second part. Specifically, all these API patterns turn out to need to bridge between positional and name-based expressions of the members of types. How each API pattern does that is a central question of its design.

Assume the following running example:

``` c#
public class Person
{
public string FirstName { get; }
public string LastName { get; }

public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}

In the following we'll consider extending and changing this type to expose various API patterns as we examine the individual language features.

Here's an example of using the three language features:

``` c#
var p = new Person { FirstName = "Mickey", LastName = "Mouse" }; // object initializer
if (p is Person("Mickey", *)) // positional deconstruction
{
  return p with { FirstName = "Minney" }; // with-expression
}

Semantically this corresponds to something like this:

``` c#
var p = new Person("Mickey", "Mouse"); // constructor call
if (p.FirstName == "Mickey") // property access
{
return new Person("Minney", p.LastName); // constructor call
}

Notice how the new features that use property names correspond to API calls using positional parameters, whereas the feature that uses positions corresponds to member access by name!
# Object initializers for immutable objects

(See e.g. #229)

This feature allows an object initializer for which assignable properties are not found, to fall back to a constructor call taking the properties' new values as arguments.

``` c#
new Person { FirstName = "Mickey", LastName = "Mouse" }

becomes

``` c#
new Person("Mickey", "Mouse")

The question then is: how does the compiler decide to pass the given FirstName as the first argument? Somehow it needs clues from the `Person` type as to which properties correspond to which constructor parameters. These clues cannot just be the constructor body: we need this to work across assemblies, so the clues must be evident from metadata.

Here are some options:

1: The type or constructor explicitly includes metadata for this purpose, e.g. in the form of attributes.
2: The names of the constructor parameters must match exactly the names of the corresponding properties.

The former is unattractive because it requires the type's author to write those attributes. It requires the type to be explicitly edited for the purpose.

The latter is better in that it doesn't require extra API elements. However, API design guidelines stipulate that public properties start with uppercase, and parameters start with lower case. This pattern would break that, and for the same reason is highly unlikely to apply to any existing types.

This leads us to:

3: The names of the constructor parameters must match the names of the corresponding properties, _modulo case_!

This would allow a large number of existing types to just work (including the example above), but at the cost of introducing case insensitivity to this part of the C# language. 
# With-expressions

(see e.g. #5172)

With-expressions are similar to object initializers, except that they provide a source object from which to copy all the properties that aren't specified. Thus it seems reasonable to use a similar strategy for compilation; to call a constructor, this time filling in missing properties by accessing those on the source object.

Thus the same strategies as above would apply to establish the connection between properties and constructor parameters.

``` c#
p with { FirstName = "Minney" }

becomes

``` c#
new Person("Minney", p.LastName)

However, there's a hitch: if the runtime source object is actually of a derived type with more properties than are known from its static type, it would typically be expected that those are copied over too. In that case, the static type is also likely to be abstract (most base types are), so it wouldn't offer a callable constructor.

For this situation there needs to be a way that an abstract base class can offer "with-ability" that correctly copies over members of derived types. The best way we can think of is to offer a virtual `With` method, as follows:

``` c#
public abstract class Person
{
  ...
  public abstract Person With(string firstName, string lastName);
}

In the presence of such a With method we would generate a with expression to call that instead of the constructor:

``` c#
p.With("Minney", p.LastName)

We can decide whether to make with-expressions _require_ a `With` method, or fall back to constructor calls in its absence.

If we _require_ a `With` method, that makes for less interoperability with existing types. However, it gives us new opportunities for how to provide the position/name mapping metadata thorugh the declaration of that `With` method: For instance, we could introduce a new kind of default parameter that explicitly wires the parameter to a property:

``` c#
  public abstract Person With(string firstName = this.FirstName, string lastName = this.LastName);

To explicitly facilitate interop with an existing type, a mandatory With method could be allowed to be provided as an extension method. It is unclear how that would work with the default parameter approach, though.

Positional deconstruction

(see e.g. #206)

This feature allows a positional syntax for extracting the property values from an object, for instance in the context of pattern matching, but potentially also elsewhere.

Ideally, a positional deconstruction would simply generate an access of each member whose value is obtained:

``` c#
p is Person("Mickey", *)

``` c#
p.FirstName == "Mickey"

Again, this requires the compiler's understanding of how positions correspond to property names. Again, the same strategies as for object initializers are possible. See e.g. #8415.

Additionally, just as in with-expressions, one might wish to override the default behavior, or provide it if names don't match. Again, an explicit method could be used:

``` c#
public abstract class Person
{
...
public void Person GetValues(out string firstName, out string lastName);
}

There are several options as to the shape of such a method. Instead of out-parameters, it might return a tuple. This has pros and cons: there could be only one tuple-returning `GetValues` method, because there would be no parameters to distinguish signatures. This may be a good or a bad thing.

Just as the `With` method, we can decide whether deconstruction should _require_ a `GetValues` method, or should fall back to metadata or to name matching against the constructor's parameter names.

If the `GetValues` method is used, the compiler doesn't need to resolve between positions and properties: the deconstruction as well as the method are already positional. We'd generate the code as follows:

``` c#
string __p1;
string __p2;
p.GetValues(out __p1, out __p2);
...
__p1 == "Mickey"

Somewhat less elegant for sure, and possibly less efficient, since the LastName is obtained for no reason. However, this is compiler generated code that no one has to look at, and it can probably be optimized, so this may not be a big issue.

Summary

For each of these three features we are grappling with the position-to-property match. Our options:

  1. Require specific metadata
  2. Match property and parameter names, possibly in a case sensitive way
  3. For deconstruction and with-expressions, allow or require specific methods (GetValues and With respectively) to implement their behavior, and possibly have special syntax in With methods to provide the name-to-position matching.

We continue to work on this.

Area-Language Design Design Notes Language-C# New Language Feature - Pattern Matching New Language Feature - Records

Most helpful comment

I like if (p is Person where FirstName == "Mickey") but it will confuse LINQ quieries.

What about this?

``` C#
Person p;
object obj;

if (p is Person && p.FirstName == "Mickey")

if (obj is Student s && s.FirstName == "Mickey")
```

All 170 comments

:+1: for design notes!

I second HaloFour's sentiment.

I'll echo my thoughts about positional decomposition from the other thread- I think its a step in the wrong direction. It makes the code harder to read and understand without using Intellisense. With Autocomplete, the cost of having to type property names is tiny, so why avoid it?

Are record types still on the table for C# 7.0? I notice your Person example doesn't use them. Those are by far the more interesting and useful feature to me.

I just don't understand what this gives us:

``` C#
if (p is Person("Mickey", *)) // positional deconstruction

over specifying the property names, other than saving a few keystrokes, at the, IMO, huge cost of much less readability by introducing what just seems like compiler magic where everytime I have to look up the types ctor (or GetValues method) to understand what the code is doing.
Why can this not be something like

``` C#
if (p is Person { FirstName == "Mickey"}) 

or even clearer IMO

C# if (p is Person where FirstName == "Mickey")

The object initializer trying to find matching ctor parameter names seems a bit strange too. Ok, getter only properties cannot be written to outside a ctor, but as far as I understand that is not really enforced at the clr level (eg. reflection can write to readonly fields), so why not just solve the problem this way (emit code that just does it anyways)? If that's not possible, why can we not change the clr (yes I know its a high bar, but this is C#, the posterchild language for the CLR, it seems we need to find all these workarounds instead of changing/evolving things if needed).

I guess I'm just not too hot on magic transformations that depends on argument positions and naming conventions for core language features.

PS: Sorry if this sounds like a bit of a rant, I'm sure you guys are working hard, and I love C# and most of the things going on with it.

First, let me say that I love all three suggested features. However, as @chrisaut, I also feel this needs some polishing. The positional deconstruction is a bit hard to read IMHO. I feel that this is due to the fact that positional deconstruction is of course, based on the positions of the parameters. However, that is not something that I keep in my head most of the time. Why not make things more explicit by having syntax that refers to the properties themselves?

The example provided by @chrisaut that uses the where keyword is my favorite:

if (p is Person where FirstName == "Mickey") 

I feel that this better represents what is happening, namely that you first check if p is a Person and then subsequently check if the FirstName is equal to "Mickey". If we look at the positional deconstruction example (if (p is Person("Mickey", *))), this is far less apparent to me.

Another benefit of the where syntax is that it enables all types of boolean expressions to be used. For example, we could do this to match persons which FirstName starts with and "M":

if (p is Person where FirstName.StartsWith("M"))

The positional destructuring doesn't naturally allow this as far as I can tell. It also doesn't easily allow and or or statements, which are also possible in the where syntax:

if (p is Person where FirstName.StartsWith("M") && FirstName.EndsWith("y"))

if (p is Person where FirstName == "Mickey" || FirstName == "Alice")

My final argument in favor of the where syntax (or something similar) is that to me it feels more C#-ish. While this is hard to define, I feel that the positional structuring is not explicit enough, whereas the where syntax kinda looks like the exception filtering meets LINQ.

How is the proposed deconstruction is different from?

if ((p as Person)?.FirstName == "Mickey")

Please don't do positional deconstruction. C# has always been easy to read and reason about. Like defining ref/out. The "initializer syntax" is so much easier to understand and require no extra magic. Or at least show an example where positional deconstruction would be a better option.

C# p is Person("Mickey", *)

That will generate a lot of traffic to StackOverflow :)

I like if (p is Person where FirstName == "Mickey") but it will confuse LINQ quieries.

What about this?

``` C#
Person p;
object obj;

if (p is Person && p.FirstName == "Mickey")

if (obj is Student s && s.FirstName == "Mickey")
```

@omariom Not bad at all!

C# if (obj is Student s && s.FirstName == "Mickey")

@omariom I like this one the most, it's very C#ish and crystal clear what it does IMO.

BTW I don't think the where would confuse LINQ queries, where already is a contextual keyword (only a keyword if already inside an open Linq expression). I didn't even think about Linq when I proposed it, I thought of the where used in Generics (eg. Class<T> where T : struct)

@omariom when do we get this && operator? It's seems very useful!

On a serious note your expression syntax example is a specialization of #254, albeit an interesting and useful one.

@chrisaut

Such a syntax is already proposed (#206) and would work on any existing type:

if (obj is Person { FirstName is "Mickey" }) { ... }

In simpler cases you could combine the type pattern with other conditions:

if (obj is Person p && p.FirstName == "Mickey") { ... }

The positional construction, matching and deconstruction is a feature of records. The purpose of these additional proposals is to bring some of that functionality to existing types.

This seems to have more clarity.

if (obj is Person with FirstName == "Mickey") {
}

But this proposal don't bring much readability. new Ojb() is better than 'with' expression. Anyway please don't bring special 'With' or other methods like Python does. It is awful.

@HaloFour I think you're refering to let syntax, because there is no such guard for is operator and a logical AND would do.

@alrz Ah you're right, the proposal does specifically mention when as a part of switch, match and let. So yes, normal Boolean operators would apply instead. I'll update my comment.

I don't understand why people are freaking out of the syntax in its most basic form; do you guys ever heard of the word "pattern"? And suggestions involving where and with are all ambiguous. :confused:

I agree with the rest of the peanut gallery that silent automatic decomposition based on constructors is a bad idea. I'd rather use property patterns or implement an extension method to deconstruct existing classes. New classes could get a primary constructor with restricted semantics for those cases when records aren't complex enough.

The best way we can think of is to offer a virtual With method, as follows:

public abstract class Person
{
...
public abstract Person With(string firstName, string lastName);
...
public void Person GetValues(out string firstName, out string lastName);
...

Shouldn't these be new operator declarations instead? I don't know how to explain it, but it feels wrong tying language constructs to type members without a more explicit contract. Somehow the IEnumerable<T> - select and Task<T> - async and IDisposable - using relationships are more obvious...

@dsaf

As operators or as extension methods they couldn't be virtual and it's largely necessary for them to be virtual so that derived types can correctly copy over the properties that aren't specified in the signature:

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

    public Person(string firstName, string lastName) {
        this.FirstName = firstName;
        this.LastName = lastName;
    }

    public virtual Person With(string firstName, string lastName) {
        return new Person(firstName, lastName);
    }
}

public class Student : Person {
    public int Grade { get; }

    public Student(string firstName, string lastName, int grade) : base(firstName, lastName) {
        this.Grade = grade;
    }

    public override Student With(string firstName, string lastName) {
        return With(firstName, lastName, this.Grade);
    }

    public virtual Student With(string firstName, string lastName, int grade) {
        return new Student(firstName, lastName, grade);
    }
}

...

Person person = new Student("Foo", "Bar", 98);

Person person2 = person with { LastName = "Baz" };
Debug.Assert(person2 is Student { FirstName is "Foo", LastName is "Baz", Grade is 98 });

@chrisaut

I don't think the where would confuse LINQ queries, where already is a contextual keyword (only a keyword if already inside an open Linq expression).

I meant this case:

C# from p in persons where p is Student s where s.FirstName == "Mickey" select p;

Even if it don't confuse the compiler it will confuse me )

Could the team explain the perceived value of positional decomposition?

Initial knee-jerk response - For #$@#$ sake don't do that!

We can fine tune syntax, but just write that puppy out with our archetypal point example. Any two ints or bools, much less three or four. OMG!

The rest is coming along very nicely :)

@HaloFour Understood, thanks!

1) Object initializer for immutables - maybe optional parameters in constructor (similarily as in Attribute declaration) is enough ?
var p = new Person( LastName: "Taylor");
In general I guess that implicit constructor for each public property can be generated using the very same parameter names, so it would be enough just to indicate in class declaration, that such constructor shoud exist - why not just name of class without parentheses ?
public Person;
compiles as
public Person(string FirstName, string LastName) { this.FirstName=FirstName; this.LastName=LastName; }
and when these params are optional, there is no big step to allow initializer to call such constructor.
2) p is Person("Mickey",*) OR p is Person where FirstName == "Mickey" we know that p IS Person, so why not use just p?.FirstName=="Mickey" ? I do not se any benefit in deconstruction just for eqauality. Instead of this I would appretiate methods returning multiple values - like: var (n,x) = GetMinMax(int[] row).It solves some real pain...
3) p with Person("Minnie",*) - I would suggest simplier syntax - maybe I just dislike word "with" from VB:
var r = p { FirstName="Minnie" };
That's my opinion.
Jiri

This "summary" exposes a debate that we've been having in the LDM: whether or not we should use name-matching (across distinct APIs) to drive language features, and in particular to get positional behavior. This summary describes the situation with the assumption that we _should_. The other side is that _we should not_.

API patterns for a language feature facilitate two things:

  • Provide actual APIs to call at runtime when the language feature is used
  • Inform the compiler at compile time about how to generate code for the feature

That is not the traditional role of API patterns in C#. It has always been the former (API elements that the compiler invokes). I think it would not be advisable for us to have an "API pattern" where the compiler finds the API pattern and then does something other than invoke it. All of the difficulties of inferring the latter from the former can be avoided if we just _don't do that_.

Object initializers for immutable objects

Not sure why we're discussing this. It has been low on the list of things we're likely to consider for C# 7 for some time. Is it really that much of an advantage to allow people to type more characters for an alternative way to invoke a constructor, especially with all of the semantic issues that are thereby self-created?

Positional deconstruction
Ideally, a positional deconstruction would simply generate an access of each member whose value is obtained:

That is not ideal. Properties do not have "positions". The ideal is an API pattern that is invoked by the compiler.

@gafter

I tend to agree. If the compiler is going to allow record shorthand for non-record types I'd prefer it be through the same conventions established for record types. If a typical C# record generates a With method and/or a GetValues method which are used for "withers" or deconstruction respectively then I could see those features being enabled for CLR types that expose those same methods (or through extension methods in scope). Otherwise I don't think it's worth it.

Couldn't agree more :arrow_up: :+1:

just don't do that.

As for "object initializers for immutable objects" I don't think that it will be any useful in presence of record types, considering that you still need to declare the constructor. Also, if the class requirements go beyond of a record, it woudn't make sense to provide the same syntax for deconstruction out of the box.

Positional deconstruction for tuples, yes. For records / classes with named properties - no.

I love all of this.
Would you be able to use named arguments in positional deconstruction? (named deconstruction?)

p is Person(firstName: "Mickey", lastName: *)

@jakeswenson

Why wouldn't property patterns be sufficient for that purpose?

p is Person { FirstName is "Mickey" }

@HaloFour, operators would inded kill the chance of having virtual methods. But extension methdos wouldn't. Pretty much like LINQ - operators can translate to either instance methods or extension methods.

@MadsTorgersen, @gafter, What's the use case of this?

public abstract Person With(string firstName = this.FirstName, string lastName = this.LastName);

Isn't this opening a can of worms?

Would this (or something like this) be valid:

if (p is Person(lastName: "Mouse", firstName: "Mickey")) { ... }
if (p is Person(firstName: "Mickey", lastName: *)) { ... }
if (p is Person(firstName: "Mickey")) { ... }
if (p is Person(FirstName: "Mickey", LastName: *)) { ... }
if (p is Person(FirstName: "Mickey")) { ... }

@paulomorgado

Extension methods have the exact same problem that operators do, the dispatch is determined at compile time and not at run time based on the actual type of the instance. In my example above the variable is Person, not Student, so any extension method it would resolve would be for this Person, not this Student.

@HaloFour, you can put the extension methods in any extension class you want to and virtual methods would still be possible.

The exact same thing happens with LINQ. Not all enumerables are alike.

@paulomorgado You could, but that only works based on the type of the variable since that's the only thing that the compiler knows. This is very different from virtual methods where the dispatch is determined at runtime based on the exact type:

public static class PersonExtensions {
    public static string Test(this Person person) {
        return "Person";
    }

    public static string Test(this Student person) {
        return "Student";
    }
}
...
Person person = new Student();
 // compiler resolves to extension method of Person
string result = person.Test();
Debug.Assert(result == "Person");

vs.

public class Person {
    public virtual string Test() {
        return "Person";
    }
}

public class Student : Person {
    public override string Test() {
        return "Student";
    }
}
...
Person person = new Student();
// compiler virtually calls Person.Test which is dispatched to Student.Test at runtime
string result = person.Test();
Debug.Assert(result == "Student");

If Person or an extension method for Person performs the "wither" the Grade property will most certainly be lost and the return type would be a Person, not a Student. That's why they would need to be virtual instance methods, and in the case of abstract types also abstract.

Positional deconstruction

I struggle to see how this feature justifies the complexity and new concepts it would add to C#. In just 1-line we're introducing is against a constructor, using a constructor without new, and the yet-seen-before wildcard in C#:

if (p is Person("Mickey", *)) // positional deconstruction

What are all these new concepts saving us from writing, this?

if ((p as Person)?.FirstName == "Mickey") {
}

Which is even less readable than above as you'll need to either carry the constructor definition in your head or routinely consult the class definition to find out what the condition is matching against.

Note: just because something is more terse doesn't make it more readable.

If we also wanted multiple conditions we add a static function and get readable, typed syntax today:

using static Test;
public class Test {
    public static bool match<T>(T o, Func<T, bool> predicate) => o != null && predicate(o);
}

if (match(p as Person, p => p.FirstName == "Mickey" && p.LastName == "Mouse")) {
}

With no added complexity cost to language or tooling. Tho would be nice if the compiler could optimize away the method call stack + lambda overhead for us :)

Object initializers for immutable objects

Whilst not as bad as positional deconstruction, I'm not seeing the benefits from immutable object initializers either:

var p = new Person { FirstName = "Mickey", LastName = "Mouse" }; // object initializer

From what I can tell it's just an alternative to:

var p = new Person(firstName:"Mickey", lastName:"Mouse");

var p = new Person("Mickey", "Mouse");

But worse as it relies on non-equivalent name matching, a reminder from Java with how bean properties map to Java getter/setter methods by naming convention - another ugly corner of the language.

The main friction I have with immutable objects are that they're harder to genericize using reflection, (e.g. serializers/mappers), which this feature isn't helping with.

With-expressions

Not in love with the syntax but fills a need that would require a bit of machinery without language support. My preference would probably be to go with a syntax operator variety.

p { FirstName = "Minney" }
p + { FirstName = "Minney" }
p .+ { FirstName = "Minney" }
p <- { FirstName = "Minney" }

Language Complexity

Whilst on topic of adding unnecessary language features using wildcards, I'll leave a reminder of the dangers of adding language features with the retrospect of adding wildcards to Java generics:

“I am completely and totally humbled. Laid low. I realize now that I am simply not smart at all. I made the mistake of thinking that I could understand generics. I simply cannot. I just can't. This is really depressing. It is the first time that I've ever not been able to understand something related to computers, in any domain, anywhere, period.”

a feature which led Joshua Bloch (one of the designers of Java platform) to conclude:

"We simply cannot afford another wildcards"

@mythz To be clear, these are features that are in support of pattern matching, and of immutable objects that we expect to have more and more of with the help of the records feature. I realize I posted them a bit out of context here, and I should have made that more clear. Over the coming days there'll be notes on those other features, to help evaluate in context.

You're seeing the sausage get made. Your feedback and that of others helps us make the right decisions in the end - just as in C# 6.

Funny you bring up Wildcards. I bear my part of the responsibility for that very feature, though in my defense I was an academic at the time. ;-) They are a perfect example of optimizing for expressiveness instead of usability.

@mythz

The wildcards proposed for pattern matching are nothing like the wildcards in Java, the latter involving generics and variance. And even Java's wildcards aren't _that_ bad, they express the variance in a pretty clever way and because you can express them at the variable and parameter level you have much more freedom than you do in .NET. Java's real problem is that the generic type inference is backwards (compared to C#) which makes generic resolution in general a massive string of four-letter-words and the compiler error messages are ridiculously cryptic.

a feature which led Joshua Bloch (one of the designers of Java platform) to conclude:
"We simply cannot afford another wildcards"

Josh's comments were regarding the addition of closures/lambda expressions to Java, which he opposed starting in 2006-2007 when I proposed them (he continued to oppose them even as a member of the JSR, preferring something based on inner classes). But lambdas seem to have worked out brilliantly for Java.

As it turns out, both @MadsTorgersen and I (@gafter) can claim some responsibility for Java's wildcards, with me as the compiler engineer at Sun driving the language implementation (for all of the Java 3, 4, and 5 changes) and Mads leading a team of academics assisting with the specification and implementation. We would have liked use-site variance, but driven by Josh Bloch's requirement to retrofit generics onto his existing collection APIs _without rewriting any of those APIs_, we were able to find no other language solution. It would have been better for Java to ignore those requirements and follow C#'s model of declaration-site variance (in and out on type parameters of interfaces), which would have resulted in a new set of _generic_ collection APIs, as it did for C#. Josh was well aware of the shape of generics at every step of the way, and played no small part in their design. Of course, hindsight is 20/20.

As has been pointed out, these "wildcards" have nothing to do with the kind of wildcards that appear in Java's type system, and they are not part of the type system in any sense.

@HaloFour wrote

Java's real problem is that the generic type inference is backwards (compared to C#) which makes generic resolution in general a massive string of four-letter-words and the compiler error messages are ridiculously cryptic.

You're welcome.

We won't do that.

@HaloFour, using extension methods does not forbide the use of virtual methods. Again: LINQ.

Inside the extension method you can also have logic to handle the correct types at runtime:

public static class PersonExtensions 
{
    public static string Test(this Person person)
    {
        if (person is Student)
        {
            return "Student";
        }

        return "Person";
    }
}

(I'm not goint into pattern matching here. Just using what we can get our hands on now).

If you are into it, you can always go with late binding:

public static class PersonExtensions 
{
    public static string Test(this Person person)
    {
        return TestInternal(person);
    }

    private static string TestInternal(dynamic person)
    {
        return TestInternalImpl(person);
    }

    private static string TestInternalImpl(Student person)
    {
        return "Student";
    }

    private static string TestInternalImpl(object person)
    {
        return "Person";
    }
}

Come to think of it, you might do the same with an operator. But I think the method invocation (which gives you the option of being an instance or extension method) is more versatile.

Of the three features, the With-expressions are the obvious biggest gain, as they simplify the creation of new immutable references/values. However, regarding,

... there's a hitch: if the runtime source object is actually of a derived type with more properties than are known from its static type, ...

the ideas behind With methods seem highly inelegant. Another option would simply to be to restrict this feature to sealed types. Whilst this would limit its use in existing types, it would fit well with records (assuming they will be syntactic sugar over structs and sealed classes) and would remove the whole issue of derived types.

I really like the idea of extending object initialisers to allow constructor parameters to be specified with the same syntax as properties. However, rather than ideas around ignoring case in this instance, a simple style convention change could suffice: parameter names for immutable types should match the read-only properties exactly for both this feature and with-expressions to work. This could also then address @mythz's comments about immutable types and serialization.

The positional deconstruction feature makes most sense for tuples. For records and traditional types, less so. As others have commented, this could lead to less readable code all too often.

I'm pleased to read @MadsTorgersen's comments that more design notes, around other language features, are coming soon. It will help to put the ideas in this note into perspective. Plus, as many of us are keen to know what's happening with tuples, discriminated unions, records and pattern matching, this will help. I'm particularly concerned by my reading of @gafter's remark in #9375

Related features possibly to be added at the same time as pattern-matching:
#5154 expression-based switch ("match")

Which I read as pattern-matching expressions are still only being considered, whereas expression statements (the bloated switch statement) will definitely happen. Hopefully, this is a misreading of his words, but other design notes should clarify that.

@paulomorgado

Neither of those strategies are virtual methods, they are type-based dispatch and they are both completely locked to the types explicitly built into the implementation. It would be impossible to add derived types in other assemblies and to "override" those extension methods, regardless of the dispatch strategy.

This is a problem with "wither" methods and non-sealed types in general. It would be way too easy to accidentally call a base version which ends up "decapitating" the result to an instance of the base class.

https://github.com/dotnet/roslyn/issues/5172#issuecomment-147881628

Question: will the immutable objects be also possible with struct s? (a bit like Javas Valhalla project?)

@gafter and others: I did not intend for this summary to imply that we agree to do this with name matching. That is an open debate. For both with-expressions and positional deconstruction I listed the alternatives we discussed. I apologize if you feel I didn't give fair treatment to that side.

I realized after posting (and upon seeing some of the negative comments on using name matching) that I did not give due space to an option we discussed earlier, involving variants of the builder pattern. I will write that up shortly.

I wrote up #9411 as an alternative way to implement these patterns, based on using tuples and the builder pattern. I wrote it as a proposal rather than design notes, because we haven't had the same degree of discussion of it on the language design team yet.

Would love your input.

@DavidArno Nothing is set in stone. Our current plan of record is that pattern matching will be integrating into existing language constructs (is-expressions and switch statements) _as well as_ a new expression form which we call a match expression.

As we experiment with things, those plans may change. We may choose to add patterns in _more_ places (for instance, let-statements have been proposed), or we may trickle them in over multiple releases (e.g. adding match expressions later).

The way this stuff works is that we're in design mode till we ship. We ship things we have high confidence in, and drop things we don't, sometimes last minute. We publish our design notes so that we can get feedback along the way. This means that you get to see a bunch of intermediate states of design; a lot of decisions that end up being reversed later: you see the sausage getting made. Hopefully that's entertaining and useful. :-)

Make sense?

Mads

What are all these new concepts saving us from writing, this?

@mythz I think in the small examples that people have been providing, there is only marginal value. The real benefit comes when you need do much more "matching" in a single expression. A canonical example of that would be where you're trying to actually match a complex tree structure. Patterns already make this cleaner. Furthermore, if we want to both match _and_ be able to then use the sub-pieces that have been matched, then patterns are a _huge_ boon.

Take a red-black tree as an example, and let's say you're trying to match against this form:

image
which we want to transform to:
image

Trying to do so be pretty unpleasant in an imperative fashion. But with a pattern i can write something like so:

tree is Node {
  Color is Black,
  Val is var x,
  Left is var a,
  Right is Node {
    Color is Red,
    Val is var y,
    Left is var b,
    Right is Node {
      Color is Red,
      Val is var z,
      Left is var c,
      Right is var d
    }
  }
}

If i had to do all that decomposition myself it would be extremely tedious and onerous.

Now, i personally even find the above to be rather heavyweight. If i'm in my own domain, and i know that a node a quad of a "color, value, left node, right node" then i might even prefer just stating this more succinctly as:

tree is Node(Black, var x,
    var a,
    Node(Red, var y, 
        var b,
        Node(Red, var z, var c, var d))))

I can now easily see what shape this matches against. Specifically a "black" top node, with a right Red child which itself has a right Red child. The constituent pieces i need are now captured in variables (with the right types) that i can now use. It's super clean and much easier to read than the existing idiomatic C#.

In this particular example, once i matched my pattern i would be actually want to create a new node like so: new Node(Red, y, new Node(Black, x, a, b), new Node (Black, z, c, d))

Having had to write this sort of code imperatively in C# is much worse. I would have to instead write:

(tree as Node)?.Color == Black &&
(((Node)tree).Right as Node)?.Color == Red &&
(((Node)((Node)tree).Right) as Node)?.Color == Red {
    var topNode = (Node)tree;
    var rightChild = (Node)topNode.Right;
    var rightGrandChild = (Node)rightChild.Right
    new Node(Red, rightChild.value, new Node(Black, topNode .value, topNode.Left, rightChild.Left), new Node (Black, rightGrandChild.Value, rightGrandChild.Left, rightGrandChid.Right))
}

Or, if i wanted to avoid all those casts in the expression, i'd have to do things like:

if ((tree as Node)?.Color == Black) {
  var topNode = (Node)tree;
  if ((topNode.Right as Node)?.Color == Red) {
    var rightChild = (Node)topNode.Right;
    if ((rightChild.Right) as Node)?.Color == Red {
       var rightGrandChild = (Node)rightChild.Right

And all of that code is just for _one_ case that i'm matching against. In practice there would be very many more.

And thank goodness i at least have ?. for C# 6.0 to help me out here. Having to write this without that feature is even more painful :)

@MgSam > With Autocomplete, the cost of having to type property names is tiny, so why avoid it?

I'm not certain i personally agree with that. Take the possible C# 7.0 examples i listed above. Haivng to write the name increases the size of hte pattern from 84 to 191 chars. That's more than twice the size, and makes it nearly impossible to fit cleanly in a single line (or even on a few lines).

Now, i am not arguing that positional matching makes sense in all cases. Nor would i ever insist that you must use, or that you should not use named pattern matching.

However, positional matching can be very valuable in certain pieces of code _as determined_ by the author of that code. If this is my code, and i know what the shape of a node is, then making me specify all the names makes the code very heavyweight compared to the version where i eschew it.

I'd like to make what i think is an appropriate analogy here. Deconstruction is the reverse of construction. When i _construct_ something, there is no requirement that i provide names for things. For example, today, in my red-black library, i would definitely write:

new Node(Red, someVal, oneNode, otherNode)

In that code i never specify the names of those parameters. Previously you mentioned:

It makes the code harder to read and understand without using Intellisense

And that's true. But we still live with it because having to type this

new Node(color: Red, value: someVal, left: oneNode, right: otherNode)

at every construction site would be excessive. We certainly _allow_ people to be that expressive when constructing things. But we do not require it, despite that meaning there is less information in the code for you to tell what's going on.

As deconstruction is the reverse of construction, i feel the same holds true. If you _want_ to deconstruct using names, by all means do so! If you feel it makes your code cleaner and easier to read, then by all means go right ahead.

However, like with construction, if you feel like such explicitness is unnecessary and heavyweight, you can also avoid it if you want. in other words, feel free to write:

x is Node { Color is Red, Val is var val, Left is var left, Right is var right } _or_
x is Node(Red, var val, var left, var right)

Whichever feels best to you given what you're doing. Personally i think the latter form is much nicer for this domain. But i wouldn't insist that everyone would have to use it for all domains.

@CyrusNajmabadi Not sure if there is a "compiler writer bias" with this feature, but I can't recall many times in the last decade where I've thought there was enough friction that would justify needing and learning a new "foreign" language feature for it.

The complex match expression looks fragile (and IMO not particularly readable) where I'd expect it would be hard to workout why an arbitrary data graph doesn't match a complex nested match expression. I'd also not expect debugging or tooling/intelli-sense to be able to provide as much assistance as it could with multiple statements.

IMO the minimum syntactical complexity we could add to make this more pleasant would be to use an alias (mentioned earlier in this thread), so instead of:

if ((tree as Node)?.Color == Black) {
  var topNode = (Node)tree;
  if ((topNode.Right as Node)?.Color == Red) {
    var rightChild = (Node)topNode.Right;
    if ((rightChild.Right) as Node)?.Color == Red {
       var rightGrandChild = (Node)rightChild.Right

We'd be able to do:

if (tree as Node top && top.Color == Black && topNode.Right as Node right && right.Color == Red)
{
    if (right.Right as Node rightChild && rightChild.Color == Red) {
        if (rightChild.Right as Node rightGrandChild) { ... }
    }
}

Which I believe is a "more natural fit" for C# that would also be better to benefit from existing tooling. I also believe this is more readable as it more closely matches how the written code would be interpreted in our brain as we're skimming through the source code.

but I can't recall many times in the last decade where I've thought there was enough friction that would justify needing a new "foreign" language feature.

There was never much friction with null checking either. And yet, the addition of ?. is still wonderful for just smoothing out all those little bumps :)

The complex match expression looks fragile

Really? Why?

We'd be able to do ... Which I believe is a "more natural fit" for C#

That approach is nearly 3x longer than the simple positional form. While i don't believe language features should be decided solely based on their terseness, i do believe that we should strive for simpler ways to express code that does require so much cruft to be built up. Consider the simple case of "anonymous methods" and "lambdas". We could have lived with anonymous methods. After all, it was just a few more characters over a lambda. However, at the end of the day, those extra characters just end up smeared all over your code (an effect i call "peanut butter syntax" :)).

In this case, i find yoru code _much_ harder to match (no pun intended) against the actual tree i care about. And i think i can qualify why this is. In order to understand your expression, i have to both read left to right _and_ simultaneously see how names are introduced when later checks are done. In other words, you need to introduce named temporaries _solely_ for matching _even though they're never specified in the image, nor are they needed at all later on_. This itself adds cognitive burden. In the pattern approach i've given i only name things that i actually want to use later on (namely, "a, b, c, d, x, y, z"). Nothing else needs a name, needs to be put in scope, or needs to be understood.

That allows my code to far more closely match the _data_ i'm trying to work against. In other words, like linq, i get to think about that "what" not the "how". I get to simply express what i'd like to match against, and what i need to use. I very much see patterns as an analog to linq. With linq i got to move away from the "how" of working with collections. The "how" of iteration and temporaries, and building up results. "Patterns" are the same for me. I get to finally move away from teh "how" of checking if something fits what i'm looking for and the "how" of pulling out all the state i need. And i can think about "what" i'm doing.

Finally, wrt to positional deconstruction, consider the analogy i made with construction. All the arguments made against positional deconstruction could have been made against positional construction as well. Would we prefer C# if it mandated that you write parameter names every time you construct an object? IMO, no. It would be paying a cost over every line of code you write that would provide value in only some places.

I'm a big believer in giving the language tools to enable both. I don't think either of named or positional is superior to the other. I think there are places where both work great. But i do think when you only have one and not the other, you lose out big on having the tools to do the right thing for any piece of code you may end up writing.

@mythz

The complex match expression looks fragile (and IMO not particularly readable) where I'd expect it would be hard to workout why an arbitrary data graph doesn't match a complex nested match expression. I'd also not expect debugging or tooling/intelli-sense to be able to provide as much assistance as it could with multiple statements.

F# has had positional deconstruction for more than a decade (since v1.0) and I've never once heard these complaints. It's seen, almost universally, as a win in productivity and expressiveness.

@CyrusNajmabadi I take your point about positional construction vs deconstruction, but let's not forget, positional construction as we know it now has existed in programming languages for 40 years, and when it was designed the size of your code was a very real concern, so named arguments were infeasible. Not to mention IDE tooling was non-existent. C# 1.0 - 3.0 inherited this legacy; it's not like you could have made positional arguments illegal once named arguments were added in v4.

However, positional matching can be very valuable in certain pieces of code as determined by the author of that code. If this is my code, and i know what the shape of a node is, then making me specify all the names makes the code very heavyweight compared to the version where i eschew it.

In a modern programming environment there is no reason to ever not be using named arguments because the tooling can/should make it incredibly easy for you do to do so. There's no need to type the names of the arguments; let a tool fill them in for you.

When you design features to save the programmer a few keystrokes but are to the detriment of later readers of the code you end up with a write-only language like Perl. You may have memorized what each argument means in new Node(Red, someVal, oneNode, otherNode) but you shouldn't assume that your co-worker (or future you!) will have it memorized.

@mgsam > I take your point about positional construction vs deconstruction, but let's not forget, positional construction as we know it now has existed in programming languages for 40 years

So has positional deconstruction :)

@CyrusNajmabadi Yes but it's not a breaking change to leave it out of C# 7.0.

In a modern programming environment there is no reason to ever not be using named arguments

And this is where we disagree. I don't think it's my place to prescribe to people that there is no reason to ever not be using named arguments. I think it's totally up to the consumer to make the decision. As you can see from Roslyn itself, the _vast_ majority of our code does not use named arguments. Indeed, we use it sparingly in the places where we think it adds readability. :)

I think the language should provide the tools, and we should allow developers to use these tools as best as possible for their own domains.

When you design features to save the programmer a few keystrokes but are to the detriment of later readers of the code

"detriment" is very ambiguous :)

I can't think of a single feature we've _ever_ added that someone couldn't argue was detrimental for later readability :)

Really? Why?

Because it would force our brains to match the nested data graph with the complex nested match expression in a single pass. I believe source code should optimized for read/skim-ability where the less times we need to use our head as a calculator and memory bank, the better - there are much better places we could be spending our limited complexity budget on. To give more context on this, I believe programming languages and APIs should be optimized for learnability.

That approach is nearly 3x longer than the simple positional form.

Saying something is longer doesn't say anything about its readability (e.g. 1 char vars used in positional example) as what's missing could easily be context that your head needs to invisibly carry as it's trying to parse the expression. What you should be measuring is the amount unnecessary artificial machinery required, which I'm not seeing much of.

I'm a big believer in giving the language tools to enable both.

So is C++ whereas more enjoyable languages like Python prefer one way of doing things.

@MgSam That doesn't really contradict what i saying :)

Positional construction has existed for decades. So has positional deconstruction.

C# had positional construction no one seemed to mind. And we even added named construction later (ostensibly for COM, but i actually think it's useful all the time).

"positional" is extremely natural in C#. It's everywhere. Look at 99.9% of all method calls out there. Positional. Look at 99.9% of object construction: Positional. Indexing: Positional. Attributes: Often positional (though often named as well).

I _personally_ would find it extremely _unnatural_ to say: you can construct an object positionally, but you can't deconstruct it positionally. Look back at my actual example. We allow, and primarily see people writing: new Node(Black, val, newLeft, newRight). But to deconstruct, i'd now be _forced_ to name all the values? That seems very inconsistent and onerous. In my own library i'd have to write "Color/Val/Left/Right" over and over and over again. Indeed, in the example i gave where i was doing just _one_ of at least a dozen pattern matches, i needed to write those names _12_ times. For my domain that was 12 things i had to write during deconstruction that i didn't have to write during construction. Instead of deconstruction being as natural and easy as construction, it becomes decidedly harder and less pleasant for my domain.

Again, not an exaggeration. An actual use case.

@FunctionalFirst

F# has had positional deconstruction for more than a decade (since v1.0) and I've never once heard these complaints. It's seen, almost universally, as a win in productivity and expressiveness.

Notably the identifier pattern is only really applicable to discriminated unions and active patterns which have a positional definition to them. Arguably the record syntax proposed for C# has more in common with an F# active pattern with a single partition so position matching/deconstruction makes sense there.

You may have memorized what each argument means in new Node(Red, someVal, oneNode, otherNode) but you shouldn't assume that your co-worker (or future you!) will have it memorized.

This is literally the world today for C# en masse :)

The language has survived _great_ to this point despite having positional parameters. Many other languages which _only_ have positional parameters have worked out fine. Many modern _recent_ languages have come out, been quite popular, and still only have positional forms. :)

Positional does not actually seem to be a great detriment to readability or maintainability. And, indeed, the C# development community has not appeared _at all_ to have sided with the idea that they should avoid it and only stick with named parameters. As such, i'm extremely hesitant to use _that_ as the reasoning for why we should avoid positional deconstruction.

@CyrusNajmabadi

I don't think it's my place to prescribe to people that there is no reason to ever not be using named arguments.

C# makes many choices to restrict what the developer can do. "Pit of quality" and all that. If a developer wants a free-for-all language where every feature under the sun has been added and you can dig yourself into an inescapable hole at the drop of a hat they can use C++.

As you can see from Roslyn itself, the vast majority of our code does not use named arguments.

I also don't think that because MS's style internal style guidelines have allowed or disallowed something makes it the correct decision.

I know nearly every time I write a method call where I've elided named arguments, the next time I look at that code again I've already forgotten what each parameter is. The problem is even worse when all the arguments are of the same type. Take Regex.Replace for example. Can you tell me the order of the 3 string arguments without looking at the docs? I use it almost everyday and I still forget.

But I digress. The positional vs named arguments has already sailed.

I just don't think positional deconstruction adds anything besides confusion. You again mentioned that you'd have extra characters in there- so what? A _tool_ should be generating those characters for you. A _tool_ should be formatting it onto separate lines for you so that its easily readable. The _language_ shouldn't need a dubious feature because the tooling is not robust enough.

I also don't think that because MS's style internal style guidelines have allowed or disallowed something makes it the correct decision.

It's the decision of the greater community. The coding style you are espousing has not been adopted by the community at large. You seem to be arguing: I want C# to be used in this manner. And because of that, i don't like that you're adding this feature.

I'm arguing: the C# community has shown exactly how they use this language. As such, this feature makes sense given how people actually use the language.

I just don't think positional deconstruction adds anything besides confusion.

The same argument could be made about positional construction. Or positional invocation. But _in practice_ it does not seem to actually be an issue. And this is the case across a _huge_ number of languages.

I'm trying to figure out why positional is fine for all these other cases but suddenly doesn't "add anything but confusion" to the deconstruction case. _Especially_ as we have numerous languages that use positional deconstruction as well.

You again mentioned that you'd have extra characters in there- so what?

I believe extra characters have to justify their worth. Take, for example, anonymous methods versus lambdas. We coudl have never shipped lambdas, and we could have instead forced people to write things like this:

.Select(delegate (x) { return x.Age > 21; })

We did argue about these "extra characters" and we did have arguments about "so what". And, the result, at the end of the day was that the was enough value in not forcing people to add all that ceremony to do something we thought was very useful. To me, positional fits into the same bucket. I need a lot more ceremony which i would prefer to not have in my example when i deconstruct objects. Just as named parameters would be a lot more ceremony when i construct objects.

I wholeheartedly approve of having the named forms so that people can have the names _if_ they find them useful. I'm just loath to force people to have that ceremony if they don't feel like it adds anything to their code.

At the end of the day, i think the choice should be in hte hands of hte developer. For you and your team you can absolutely state that you will never use the positional form (just as i assume you do the same for all other positional constructs). That's totally fine! :) But for developers that find the ceremony too excessive, they can always choose the other form. :)

I think these are comparable samples:

//deconstruction/positional syntax
if (tree is Node(Black, var x, var a, Node(Red, var y, var b, Node(Red, var z, var c, var d))))) {
    return new Node(Red, y, new Node(Black, x, a, b), new Node (Black, z, c, d));
}

//property pattern syntax
if (tree is Node {
    Color is Black,
    Val is var x,
    Left is var a,
    Right is Node {
        Color is Red,
        Val is var y,
        Left is var b,
        Right is Node {
            Color is Red,
            Val is var z,
            Left is var c,
            Right is var d
        }
      }
  }) {
    return new Node(Red, y, new Node(Black, x, a, b), new Node (Black, z, c, d));
}

//type pattern syntax
if (tree is Node top && top.Color == Black
    && top.Right is Node right && right.Color == Red
    && right.Right is Node rightChild && rightChild.Color == Red) {
    let x = top.Val;
    let a = top.Left;
    let y = right.Val;
    let b = right.Left;
    let z = rightChild.Val;
    let c = rightChild.Left;
    let d = rightChild.Right;
    return new Node(Red, y, new Node(Black, x, a, b), new Node (Black, z, c, d));
}

//C#6
var top = tree as Node;
var right = top?.Right as Node;
var rightChild = right?.Right as Node;
if (top?.Color == Black && right?.Color == Red && rightChild?.Color == Red) {
    var x = top.Val;
    var a = top.Left;
    var y = right.Val;
    var b = right.Left;
    var z = rightChild.Val;
    var c = rightChild.Left;
    var d = rightChild.Right;
    return new Node(Red, y, new Node(Black, x, a, b), new Node (Black, z, c, d));
}

To me, type pattern syntax seems strictly superior (with the C#6 syntax right behind it). Certainly deconstruction syntax is most terse here, but I fail to see why that deserves to be a goal.

but I fail to see why that deserves to be a goal.

It's not a goal. But it's a benefit.

To me, the goal is to give users flexible approaches to use patterns in their own code with reasonably knobs for their own domain wrt expressiveness/terseness/etc. Positional patterns happen to be one piece of that goal

Note: We went through a similar thing (with similar debates) in the linq timeframe for query expressions (i.e. from x in ... where ... select ...). Technically you don't need them. Indeed, as hte language spec shows, they're nothing more than sugar over writing the standard method calls yourself. But we felt there was enough value in bringing this more succinct way for working with your data sources vs just the method+delegate argument approach.

It was never a "goal" to get this specific feature in. But it still was felt to be a benefit to get it for all the c# developers we thought woudl be using linq.

To me, the value of positional deconstruction is definitely there. Even the next closest example in what you listed (the other pattern matching form) is literally twice as long. It's twice as long, and for my domain adds nothing but ceremony. I know in my domain that it's Color/Value/Left/Right. Indeed, that's why i can write:

new Node(Red, y, new Node(Black, x, a, b), new Node (Black, z, c, d)) instead of the much more unwieldy:
new Node(color: Red, value: y, left: new Node(color: Black, value: x, left: a, right: b), right: new Node (color: Black, value: z, left: c, right: d))

In some places in code i might find the latter beneficial. But i'dnever require anyone to write it. And i'm loath to do the same with deconstruction. deconstruction would no longer be symmetric with construction, and it would not face a large 'ceremony' tax every time you wanted to use it.

@CyrusNajmabadi

I'm trying to figure out why positional is fine for all these other cases but suddenly doesn't "add anything but confusion" to the deconstruction case. Especially as we have numerous languages that use positional deconstruction as well.

Context, readability and the ability to reason about code. When looking at a piece of code you want to understand what it does quickly. Construction is easy, new Foo(a, b, c) will give you a Foo object and a, b, c is not that important to understand the flow of the code. Similar for invocation, you can DoThatThing(x, y, z) and understand that it will "do that thing". The function name give context and clarification, even when used in a condition like if (MyConditionHolds(x, y, z)). You only need to look at the function name when browsing the code.
But when you now instead get
if (p is Person("Mickey", true, *))
there is no longer any context for "Mickey", true which is the important part of the statement. And yet they are the two most important parts of the expression for understanding the condition.
if (p is Person p && p.Name == "Mickey" && p.IsAuthorized)
Personally I would always prefer the latter when browsing someone else's code.

type pattern syntax seems strictly superior

Then feel free to use that syntax :)

None of my arguments state that that syntax should not be offered or available to people who want to use it. If it's the best for you _terrific_. That's why we have several forms for whatever feels best.

Context, readability and the ability to reason about code. When looking at a piece of code you want to understand what it does quickly. Construction is easy, new Foo(a, b, c) will give you a Foo object and a, b, c is not that important to understand the flow of the code.

Er... why is it not important? All the same reasons people gave before seem to apply here. What is 'a'? Is it correct at the first argument? How can i tell just from reading the code? What about 'b' and 'c'? As mgsam stated:

"I know nearly every time I write a method call where I've elided named arguments, the next time I look at that code again I've already forgotten what each parameter is."

For him not having parameter names in the code means he forgets _nearly every time_.

Personally I would always prefer the latter when browsing someone else's code.

And MgSam would personally prefer that everyone uses named paramters for all invocation/construction expressions. :)

(and when we did generics we had people who said they were prefer that they never look at code with generics in it).

As i've mentioned already. I have no problem with people being able to choose _for themselves_ what parts of the language they want to use. But i'm loath to insist that everyone else must follow. Mgsam would prefer no one use anything but named parameters. But i don't think others feel the same way.

You'd prefer to use names your 'Person' case, and i'd have no problem if you did that. But i'd prefer to _not_ have to put in all those names in _my_ code. Giving people these tools allows all the different styles to be satisfied. Keeps things nicely parallel with construction. i.e. if you can understand your code that uses construction without names, then you can understand your code that uses deconstruction without names. Similarly, if you use names wiht construction, then you'd probably want names with deconstruction.

So, for your example, i would have always constructed it as: new Person(..., authorized: true), i would never write new Person(..., true). And my teammates can back me up on this. Whenever there's just some random constant that doesn't not provide enough context, i always ask them for a named parameter. Given that i'd use named parameters on construction, i'd almost certainly use it on deconstruction as well. However, for tons and tons of cases where i didn't bother with names on construction (because it's clear from context), i would have no problem with either approach.

It's interesting how much of this seems to be arguing against pattern matching in general and not of positional deconstruction. I wonder if that's due to the unfamiliar syntax or just the limited examples that have been demonstrated which could almost as easily be handled by type checks and null-propagation. I think some of the blame also falls on the limited subset of patterns that have been proposed thus far. No array/list/dictionary patterns, no and/or patterns, no parenthesis/associativity patterns and no active patterns. It's arguing for a half-empty toolbox when you already have a hammer.

My only concern with positional deconstruction is how the properties are related to their positions. I don't like the idea of voodoo applying here. I think that a developer, either the author of the type or the author of an extension method, should actively define the relationship through declaring a method which returns the deconstructed form. I'm (of course) partial to the operator is syntax I proposed in #9005. I don't think that there needs to be a second GetValues form if we plan on having is operators.

@CyrusNajmabadi

It's hte decision of the greater community. The coding style you are espousing has not been adopted by the community at large. You seem to be arguing: I want C# to be used in this manner. And because of that, i don't like that you're adding this feature.

If a feature exists you can be guaranteed people are going to use it. Positional arguments already exist. Let's face the facts. Most developers don't spend a whole lot of time thinking about writing easy to read, provable correct code. That's why you guys have to design C# as a "pit of quality"; to save developers from their own human fallibility. Just because a feature is widely used doesn't mean it would have been the right choice if you were designing the language from scratch today; given experience and 20-20 hindsight.

Look at object initialization syntax. That has no positional form. But why not? The properties are written in some order when you type them into the file, so why not allow objects to be initialized with that ordering? Or maybe alphabetical, like they're displayed in Autocomplete? It would certainly save keystrokes. No one even suggests such an idea though, because it would be a nightmare for readability.

I believe extra characters have to justify their worth. Take, for example, anonymous methods versus lambdas. We coudl have never shipped lambdas, and we could have instead forced people to write things like this:

But this is not a good comparison, because lambda syntax, though terse, did not introduce any _ambiguity_. Positional syntax is _entirely_ ambiguous; it requires documentation (either in-tool or external), it is brittle, and it is hard to read. There's no inherent reason it is Regex.Replace(input, pattern, replacement) rather than Regex.Replace(pattern, input, replacement).

I think some of the blame also falls on the limited subset of patterns that have been proposed thus far. No array/list/dictionary patterns, no and/or patterns, no parenthesis/associativity patterns and no active patterns. It's arguing for a half-empty toolbox when you already have a hammer.

These are just LDM notes. They're just exposing the current stuff we're discussing. Nothing is finalized. It is not intended to completely cover all cases. We find that it's much easier to do language design if we bite things off work on it a while, and move forward. Trying to solve the entire space all at once is usually not a great way to do things.

My only concern with positional deconstruction is how the properties are related to their positions. I don't like the idea of voodoo applying here.

There are many things we are considering around positional deconstruction. One is some way of having the compiler do the hookup (and i've been tasked with coming up with some of the proposals there). The other is to have things explicitly provided by the API author. We also strongly think that if the API author provides things explicitly, then the compiler never does any sort of hookup on its own. But none of this has been decided on. :)

Positional syntax is entirely ambiguous;

And yet, pretty universal :)

Indeed, just above you, you have another people claiming that it's totally clear to do "new Foo(a, b, c)".

I get that it contains less information than the named form. I do not question or argue against that. My point is simple that programming languages have nearly all universally been able to work with that limitation without serious problem across time and across pretty much the entire development community.

I also find your argument somewhat confusing. You previously said: "A tool should be ...".

Given that you are heavily pushing the idea that people use tools, then the issue is very non-problematic. The tooling can always tell you what properties these positional pieces match against. As such, there is no ambiguity.

Now, if you're arguing about how readable the code is in the absence of tooling, then that's another story altogether. But, if you're arguing that, then we have _lots_ of features that suffer from this today, including (but not limited to):

  1. Invocation. Without tooling, you don't know what parameter each argument maps to.
  2. Construction. Same as above.
  3. var. No way to know the type.
  4. simple lambdas. No way to know the parameter types.
  5. linq queries.
  6. No way to know if an operator is overloaded.
  7. No way to know what the following are: N.N.C.C.P.F. Which of those are namespaces, classes, properties or fields? Without tooling, it's really hard to tell at teh language level.
  8. etc. etc.

In the absence of tooling, the language already contains a large swath of _useful_, _widely adopted_ functionality that can be hard to read. As such, i don't see a problem with adding something else. And, in the presence of tooling, there is no issue because tooling can tell you everything you need to know. Just as sig-help tells you what parameter your argument matches, we could have similar tooling that tells you what property your pattern matches against.

C# has _since around V3_ moved far away from the "code must be extremely explicit and unambiguous" without tooling. (i know, i had to do the IDE work to make sure the experience was good around all these features ;-) ). That ship has definitely sailed.

@CyrusNajmabadi

These are just LDM notes. They're just exposing the current stuff we're discussing. Nothing is finalized. It is not intended to completely cover all cases. We find that it's much easier to do language design if we bite things off work on it a while, and move forward. Trying to solve the entire space all at once is usually not a great way to do things.

Of course, and the spec does drop hints at some of those missing patterns. Although the conversation around and/or patterns hasn't been particularly positive (which is a shame, I honestly think those two would be a massive benefit). But if people aren't that familiar with pattern matching in other languages (and honestly, that includes myself) then not seeing how it applies in more complex cases might not demonstrate their value over imperative C#.

We also strongly think that if the API author provides things explicitly, then the compiler never does any sort of hookup on its own. But none of this has been decided on. :)

Indeed, but I'm in the camp that if the API author doesn't provide things explicitly them the compiler never _should_. On top of that I think other developers should be able to fill in those gaps. There is precedent in LINQ and in async/await of the compiler allowing extension methods to be resolved. You can make literally anything "awaitable", why not the same for "deconstructable"? And it's a bonus that it's similar to single partition active patterns.

Indeed, but I'm in the camp that if the API author doesn't provide things explicitly them the compiler never should.

That's completely fair, and certainly is something we're considering.

On top of that I think other developers should be able to fill in those gaps. ... You can make literally anything "awaitable", why not the same for "deconstructable"?

So do we. We're definitely considering ideas where this is possible.

That said, the open discussion is how things work for the literally millions of lines of existing code that exists out there. One option is to say "you can't use patterns with it unless the API author opts in, or you go and add those extensions to light it up". Another option is to say "patterns can light up on existing code without people ahving to do anything."

It is, of course, the age old "is this opt-in or opt-out" discussion. There are good arguments for either side, but there's also questions of pragmatism, value, and whatnot.

Note that we have precedence for lighting stuff up on code that never opted into anything. For example, collection initializers just light up on code that happens to derive from IEnumerable and has an "Add' method. The idea there was that tehre was enough value in just detecting and supporting this pattern (no pun intended) vs forcing all existing 'collection like' types to have to opt-into our collection initializer feature.

In that case, we felt there was just too much value in being able to light this up without needing everyone to have to add "Add" extension methods in their codebases. Personally, i think we made the right choice there. I think it was pragmatic and meant maximal benefit without little downside.

@CyrusNajmabadi

Note that we have precedence for lighting stuff up on code that never opted into anything.

I don't disagree, but automatically "lighting up" positional deconstruction via case-modulo constructor/property correlation seems quite flimsy and assumes that there may be relationships between them at all. It's quite common for the shape of the constructor and the shape of the properties to be wholly or partially unrelated. It's also common for the constructor arguments to be transformed prior to being exposed through properties, but you're comparing the property values in what otherwise feels like constructor syntax. At what point does the compiler say, "Nope, these constructors don't match the properties enough, so we can't positionally deconstruct this type." And I worry about the false positives a lot more than I worry about the false negatives.

Anyway, these are all known arguments against that aspect of the feature.

“It's also common for the constructor arguments to be transformed prior to being exposed through properties”

Doesn’t that present a problem for both named and positional deconstruction?

Anyway, these are all known arguments against that aspect of the feature.

Yup!

We'll definitely only want hookup if we think the amount of true positives is sufficient and hte amount of false positives is acceptable low. i.e. the same approach we took for collection initializers.

Personally, i would take a somewhat conservative approach. I.e. the match should be very easy to explain. As a starting point it might just be something like: the matching property much exist and must have the exact same type as the parameter. This could _eliminate_ some cases that we might like, but could still keep enough value with existing codebases out there.

For those types that didn't fit this conservative approach, the API author would either have to opt in, or we'd need some other mechanism (like extension methods) for third parties to light things up there.

@KathleenDollard

I don't believe so, property patterns use the property names directly so it should be more clear that the pattern is applied to the property values.

@KathleenDollard

Doesn’t that present a problem for both named and positional deconstruction?

Not for named deconstruction. With named deconstruction, you are stating which properties you care about.

For positional it _may_ present a problem _depending_ on how you implement positional deconstruction. For positional deconstruction that _only_ involves some sort of "automatic/implicit" matching, then it would be problematic. However, we're of the position that we'd always provide some way to do positional with _explicitly defined_ matching. If you have such a system, and are using it with the API, then there would be no problem and things would work regardless of how the actual class transformed data from teh constructor.

In other words, you can always provide some sort of explicit mechnism to support positional deconstruction. In that case, there is never any "problem" when using positional deconstruction. However, in the absence of the code providing that mechanism, we are _consider_ having a way of providing some sort of automatic system that would allow patterns to still light up. That mechanism has _not_ been spec'ed/defined/etc. But the feedback here matches our own internal thoughts both around:

  1. is such a feature desirable?
  2. if we included such a feature, what's the right way to do it?

@HaloFour

My only concern with positional deconstruction is how the properties are related to their positions. I don't like the idea of voodoo applying here. I think that a developer, either the author of the type or the author of an extension method, should actively define the relationship through declaring a method which returns the deconstructed form.

Your concern is the crux of an issue we're struggling with right now. I happen to agree with you, but the issue isn't settled.

I'm (of course) partial to the operator is syntax I proposed in #9005. I don't think that there needs to be a second GetValues form if we plan on having is operators.

We are considering GetValues as an _alternative_ to the currently specified operator is, in part because one can provide it as an extension method without adding too much machinery to the language.

@gafter

We are considering GetValues as an _alternative_ to the currently specified operator is, in part because one can provide it as an extension method without adding too much machinery to the language.

While that does make sense it seems a bit redundant to have two very similar bits of syntax, one to deal with just destructuring and the other to deal with matching and destructuring.

@HaloFour Active patterns would be driven by a static GetValues method that has a bool return type and a non-out first parameter. "Ordinary" patterns would be an instance void GetValues method. Both are independently valuable because they have different static semantics for computing whether or not a set of patterns is complete (the former can return false, but the latter cannot). Also, the non-active pattern can be done using an extension method, which is important for retrofitting existing types.

In any case, the purpose of destructuring is to support pattern-matching. The language won't use it for anything else.

What I don't get is why all of a sudden _all_ types should be compatible with positional pattern matching automatically even though they were never designed for that. @CyrusNajmabadi's tree example, in particular, does not explain that at all: The tree type would probably be defined as a record anyway, in which case positional destruction of course make sense (as long as you're used to pattern matching, that is). For all other types, just use property match expressions; if you don't like that, define an extension method (active pattern) that does the positional match for you. To me, that seems to be the far more reasonable approach than the compiler magic discussed here. This is also similar to how it works in F#. _Edit:_ As @isaacabraham correctly notes, F# only supports named matches for record types. If not even functional languages such as F# support arbitrary positional matching, why should C#? Then again, both positional and named matches are available for discriminated unions.

@mythz: Have you used pattern matching in some language before? Your objection to positional pattern matching does not seem to be about the positional aspect at all; it is rather a general objection against pattern matching. The arguments you gave _against_ pattern matching are, from my point of view, the _advantages_ of pattern matching over the imperative code you've shown...

Positional matching in F# (if I'm thinking about the same thing everyone else is) only works for tuples. For records i.e. data structures with _named fields_, you must match by name. So positional matching for tuples looks like this: -

let foo = function
| _, "bloggs", _ -> "loves C#!"
| "isaac", _, _ -> "loves F# and C#!"
| _ -> "loves F#!"

foo ("isaac", "abraham", 36) // "loves F# and C#"

With records it's somewhat different: -

type Person = { FirstName : string; LastName : string }
let foo = function
| { LastName = "bloggs" } -> "loves C#!"
| { FirstName = "isaac"; Age = 36 } -> "loves F# and C#!"
| _ -> "loves F#!"

foo { FirstName = "isaac"; LastName = "abraham"; Age = 36 } // "loves F# and C#"

I personally can't imagine how it would look to have positional matching over a named structure - it's too fragile e.g. add another field and your existing matching clauses either break or (even worse) continue to work but against the wrong field? Also - tuples tend to only have a few fields so positional matching isn't to cumbersome. Records often have many fields - you wouldn't want to positionally match against e.g. field 10 and have to leave the others blank I think. It's more readable to simply explicitly mark the fields you're matching against.

Just my two pennies.

add another field and your existing matching clauses either break or (even worse) continue to work but against the wrong field

This would be a nightmare - someone just reorders props in class in some 3rd party library, and your code compiles, but does not work...
Especially in Pocos, where a bunch of "string" props is quite offten - this would happen on regular basis.
After reading the discussion I'm getting a feeling that anything "automatic" that does not happen locally (within current block or current method) would be evil.
Everyone knows that reordering params in method is dangerous breaking change, when producing libs, that other (external) people use. This would open another door to such problems.

Why not just stuff the properties into the constructor's parameter list? There was a similar proposal during the C# 6.0 design, but for fields (https://roslyn.codeplex.com/discussions/540281).

public class Person
{
    public Person(public string FirstName, public string LastName)
    {
    }
}

The actual, generated parameter names would be normalized so that the first letter is lower-cased, and custom attributes could automatically added, too:

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

      public Person([Property("FirstName")] string firstName, [Property("LastName")] string lastName)
      {
           FirstName = firstName;
           LastName = lastName;
      }
}

@JiriZidek That's why in F# it's common to avoid using tuples with "simple" types like strings or ints, particularly when over public contract or as arguments / outputs of a function e.g. imagine the following situation where we have a function returning an (int * int) tuple: -

let data = getCustomerIdAndOrderId()
match data with
| 29, _ -> "Customer 29!"
| customerId, 520 -> sprintf "Order 520 was placed by customer %d" customerId
| data -> printfn "%A" data

now imagine the implementer of getCustomerIdAndOrderId() flips the two around: getOrderIdAndCustomerId(). Whoops - now your whole program is kaput, except the compiler can't tell us this.

Better to do something like this: -

// Simple wrapper types over int
type CustomerId = CustomerId of int 
type OrderId = OrderId of int

let data = getCustomerIdAndOrderId()
match data with
| CustomerId 29, _ -> "Customer 29!"
| CustomerId customerId, OrderId 520 -> sprintf "Order 520 was placed by customer %d" customerId
| data -> printfn "%A" data

Now getCustomerIdAndOrderId returns CustomerId * OrderId. So the type system protects us from accidentally flipping the parameters around, which you don't get with a string * string tuple, where as far as the type system is concerned they are interchangeable.

Yes, explicit type aliases - I loved them in TurboPascal & love them now in TypeScript...

type CustomerId = CustomerId of int
type OrderId = OrderId of int

Missing them in C# :-) - a bit off-topic.

@JiriZidek it's not type aliases - it's a completely different type. Type Aliases don't give you any protection - you can still mix CustomerId and OrderId as aliases. Single case DUs (wrapper types from above) are proper nominal types with a constructor for the fields etc..

@MadsTorgersen

@DavidArno Nothing is set in stone. Our current plan of record is that pattern matching will be integrating into existing language constructs (is-expressions and switch statements) as well as a new expression form which we call a match expression.

As we experiment with things, those plans may change. We may choose to add patterns in more places (for instance, let-statements have been proposed), or we may trickle them in over multiple releases (e.g. adding match expressions later).

To mind my, you have the priorities back to front. Pattern matching is a functional feature and thus is primarily geared toward expressions, not void statements. Thus, implementing match expressions should be the priority and adding it to switch should be the lower priority activity.

I would consider records, tuples, ADTs/discriminated unions and match _expressions_ as a "all or nothing" bundle. Releasing only some of these features in C# 7 and the rest in a later version makes no sense. All other proposals, including match statements. should then be "nice to haves" if there's time.

@gafter

Active patterns would be driven by a static GetValues method that has a bool return type and a non-out first parameter. "Ordinary" patterns would be an instance void GetValues method.

While I understand that using ordinary methods to perform this requires less changes to the language I seriously hope you guys do consider an operator. A normal method, particularly one with a generic name like GetValues, will end up cluttering up types and Intellisense in all of the wrong places. There are already several examples of BCL classes with either static or instance GetValues methods.

I was reading over all of these threads and I had a few thoughts I wanted to share:

It seems as though positional deconstruction and pattern matching go hand in hand.

I've used both in academia before. It was fun because it let me express things more concisely _when I wrote the code_ but I absolutely hated it when I was looking at someone else's code because I had to wrap my brain around even more information: the position of the parameters.

When I look at some of the different ideas, here are examples of how I would prefer to be able to express things:

//I like this much better than the wildcard example
if (p is Person p && p.Name == "Mickey" && p.IsAuthorized)

//This gives me a new anonymous type that has a FirstName and Age property
//You can do something kind of like that with Linq today:
//var item = from x in items select new { x.Name, x.Age };
var Info = new { Person.Name, Person.Age};

//Declares FirstName and LastName and assigns them to Person's FirstName and last name.
var {FirstName, LastName} = Person.{FirstName, LastName};

//Alternate syntax that I like more.
var {FirstName, LastName} = {Person.FirstName, Person.LastName};

//Since "With" was mentioned...
var NewPerson = Person with {
    FirstName = "Smith"
};

In all of those examples, I like them over the provided examples because, if I were to look at the code for the first time, I would be able to quickly pick up on what was happening and not have to wrap my head around all the positional stuff.

I chatted with @bjoeris about this last night. He's a polyglot geek and deep into Haskell. He misunderstood the positional syntax initially, then hated it.

If we want to do that, can we use curlies instead of parens on the positional syntax?

Of course I don't want the positional syntax at all, but the property name used to indicate any comparisons made.

I think the most straightforward way to do this would be with some sort of Option<T> struct:

public struct Option<T> {
  private T _value;
  public bool HasValue { get; private set; }

  public T Value
  {
    get { return _value; }
    set
    {
      _value = value;
      HasValue = true;
    }
  }
}

And a contextual keyword applicable to method signatures with:

public partial class Person
{
  public with Person With(
    Option<string> FirstName = default(Option<string>),
    Option<string> LastName = default(Option<string>)
    )
  {
    return new Person(
        FirstName.HasValue ?  FirstName.Value : this.FirstName,
        LastName.HasValue ?  LastName.Value : this.LastName
        );
  }
}

Along with several semantic rules:

  • When said keyword is present, the method must be named With.
  • The method must be an instance method
  • All parameters to the method must match property names accessible in the same scope as the method
  • All parameters must be this Option datatype where T is the property type
  • All parameters must have default values specified
  • The default value must be default(Option)

Of course most of those constraints could be hidden by additional syntax capabilities in the compiler and this method could be an extension method:

//as extension method
public static with Person With(this Person p,
  Option<string> FirstName = default(Option<string>),
  Option<string> LastName = default(Option<string>)
  )
{
  return new Person(
      FirstName.HasValue ?  FirstName.Value : p.FirstName,
      LastName.HasValue ?  LastName.Value : p.LastName
      );
}

//constraints handled by new syntax
public with Person With(FirstName, LastName)
{ ... }

//again as extension method
public static with Person With(this Person p, FirstName, LastName)
{ ... }

The names of the parameters to the method can provide the positions for positional patterns and the method itself can be used for with cloning.

edit:

var p = new Person { FirstName = "Mickey", LastName = "Mouse" };
if (p is Person("Mickey", *)) // positional deconstruction
{
  return p with { FirstName = "Minney" }; // with-expression
}

would be lowered as:

//still not sure how to do the object initializer exactly
var p = new Person(null,null).With(FirstName: new Option<string>("Mickey"), LastName: new Option<string>("Mouse"));
var _1 = p as Person;
if (_1 != null && _1.FirstName == "Mickey")
{
  return p.With(FirstName: new Option<string>("Minney"));
}

edit2: perhaps the With method must be an extension method and must accept null/default(T) as the first parameter to support object initialization?

What I don't get is why all of a sudden all types should be compatible with positional pattern matching automatically even though they were never designed for that.

This is not what has been proposed. I'm unaware of any proposal that would make is that "all types should be compatible with positional pattern matching".

We are _exploring_ ideas here. Ideas which may make is so that _some_ types are automatically compatible with parts of pattern matching, without having to add any new code anywhere to support this feature. This is similar to what we did with Collection-Initializers where some types just 'lit up' automatically and could not be used with collection initializers "even though they were never designed for that".

To me, that seems to be the far more reasonable approach than the compiler magic discussed here.

No compiler magic has actually been discussed. No proposal has been brought forward, and there still has not been any actual exploration of this space. it's very hard to talk about things like "reasonable approach[es]" when we don't actually have an actual proposal to discuss :)

It's actually been tasked to me to go out and see what sorts of actual algorithms could potentially be spec'ed out, and what sort of results we might get from them. If these results aren't good, then it's unlikely we would proceed in this direction. If hte results were very good, we might consider taking things, and we'd ideally be able to present a sensible algorithm and demonstrate the value (i.e. large number of true positives, low number of false positives).

I think it's a _really_ bad idea to not even bother looking into seeing what's possible. If we had done that with collection intializers, for example, we would have likely ended up with a far more limited feature that wouldn't have worked out as nicely as it does with all the cases it supports today.

My hope is to be able to get to this early next week.

it's too fragile e.g. add another field and your existing matching clauses either break or (even worse) continue to work but against the wrong field?

The same thing exists today with any method/constructor. If you change the shape of it, you can easily break things. For example:

public Point(int x, int y)

Change those around, and you've broken every person who constructs a point. Constructors are part of your public API contract. If you change them, then you run the risk of breaking code. That would remain the same if the constructor was used for construction or deconstruction purposes.

@CyrusNajmabadi As @axel-habermaier said, your example could be written as a record declaration for Node and could be deconstructed positionally right away. What this proposal is suggesting causes "non-record types" become positionally deconstructable out of the box. I'd love to see a real world example that clears up this use case where a positional deconstruction would be useful for a "non-record type" and defining extensions to make it possible is too much.

I seriously hope you guys do consider an operator. A normal method, particularly one with a generic name like GetValues, will end up cluttering up types and Intellisense in all of the wrong places.

Why would an operator _not_ end up cluttering up the types? Why would a method like this clutter up IntelliSense? We own the intellisense experience (literally, i'm one of the primary contributers to it :) ), so we can provide whatever sort of experience we want here in terms of how this is actually presented and made available there.

@CyrusNajmabadi

The same thing exists today with any method/constructor.

Yes, we all know this. And we know it is dangerous. But the things you described above would be much more vulnerable to this problem.

said, your example could be written by a record declaration for Node

But i don't want to write it as a record. I want to write it as a class :)

and could be deconstructed positionally right away

The argument i've been countering is one that stated that positional deconstruction shouldn't exist at all, in any form. MgSam, for example, does not believe that positional is ever ok. To the point that he extremely dislikes it even for normal construction or invocation.

defining extensions to make it possible is too much.

No one said it would be too much. I'd be fine with a solution that only allowed positional deconstruction through some operator, instance method or extension method based solution. I think you may not actually understand waht i've been arguing for.

But the things you described above would be much more vulnerable to this problem.

Why?

@CyrusNajmabadi

But i don't want to write it as a record. I want to write it as a class :)

Can you please elaborate why would you want to write it as a class and not a record? Perhaps there are some limitations that I'm not aware of.

@CyrusNajmabadi
Because classes typically have much more properties.

@JiriZidek And... :)

Could you be more specific about why this makes it more of an issue?

Note: i'd like to pull back a bit because it's clear there are several different arguments that are overlapping, and that's clearly causing some confusion. Examples of the different arguments i've seen are:

  1. Positional deconstruction is bad _no matter what_.
  2. Positional deconstruction is ok for some types (like records), but not ok for others.
  3. Positional deconstruction is ok if it's opt in by the type (or through some mechanism like an extension method), but not ok if the compiler attempts to use some other algorithm to somehow implicitly imbue an existing type with the ability to be positionally deconstructed.

About the only thing i've personally pushed back on is the first point. I don't think that positional deconstruction is ipso facto bad. I think it has merit and should be available as part of our pattern matching toolkit. I have not put a personal position forward on '2' or '3'. Indeed, i don't think there's enough data currently to make a decision, and i want to actually go out and investigate things next week to help provide information to best come to a personal conclusion on these two.

@CyrusNajmabadi

Why would an operator not end up cluttering up the types? Why would a method like this clutter up IntelliSense? We own the intellisense experience (literally, i'm one of the primary contributers to it :) ), so we can provide whatever sort of experience we want here in terms of how this is actually presented and made available there.

Operators are organized and managed quite separately, not just by Visual Studio (as if that's the only tool) but also by the compiler. But my complaint is more that GetValues is a name which conveys a meaning which is quite different from this one in various other contexts. What the heck does DbDataReader.GetValues have to do with deconstruction or pattern matching? Or Enum.GetValues? Not a thing.

The language is getting a metric ton of new syntax to support pattern matching. Claiming that it's worth avoiding new syntax for this case seems quite dishonest.

Note: i'd like to pull back a bit because it's clear there are several different arguments that are overlapping, and that's clearly causing some confusion. Examples of the different arguments i've seen are:

I'm going to go with 1.5, I think positional decomposition for types is always bad. Go the F# route, where positional decomposition is only possible through active patterns (operator is) and discriminated unions (enum struct/enum class/abstract sealed). Active patterns would open up the rest of the type-system in an opt-in manner. I'd also be perfectly fine if C# records gained automagic active patterns, especially since their primary constructor gives them a built-in positional property relationship.

Update: Actually, that's probably closer to 2.5. But that's splitting hairs.

Claiming that it's worth avoiding new syntax for this case seems quite dishonest.

I never made such a claim...

I was simply addressing the points about intellisense as that's something i'm intimately familiar with.

But my complaint is more that GetValues is a name which conveys a meaning which is quite different from this one in various other contexts.

Nothing is set in stone. 'GetValues' was just used to convey the idea of how this might be exposed through a method. it was never intended to come across as insisting that the name _must_ be 'GetValues'. As with most of our work that allows people to opt into a feature through some code shape, we'd assuredly try to pick something sensible and non-conflicting with existing APIs.

@HaloFour

Go the F# route, where positional decomposition is only possible through active patterns (operator is) and discriminated unions

I think this is because C# is conflating records and "named tuples" (not items, the tuple itself). For example,

class Person(string FirstName, string LastName);
// using type invocation (without `new`)
var person = Person("Mickey", "Mouse");
var tuple = ("Mickey", "Mouse");

That is clearly a named tuple, but still, nothing would stop you to deconstruct "records" via property patterns.

@bbarry,

To pick up on something you suggested, I'm a big fan of the option type, but I suspect your syntax:

public struct Option<T> {
  private T _value;
  public bool HasValue { get; private set; }

  public T Value
  {
    get { return _value; }
    set
    {
      _value = value;
      HasValue = true;
    }
  }
}

will be unnecessary. Assuming the team are working on discriminated unions (waiting for the design notes around that area, hint, hint @MadsTorgersen), it (a) should be able to be expressed simply as:

abstract struct Option<T>
{
    struct Some<T>(T value);
    struct None();
}

and (b) it needs baking into the .NET core/framework/BCL or whatever it's called this week. :)

@alrz,

At the risk of appearing a bit thick, is there any reason why tuples and records can't be the same thing if positional composition is supported for records?

public (double real, double imaginary) SomeComplexNumberFunc() => ...

is then just declaring an anonymous record?

@CyrusNajmabadi

I don't think that positional deconstruction is ipso facto bad.

I think positional deconstruction by way of some method which must run in its entirety even for partial deconstruction is ipso facto bad.

There is perhaps some syntax that enables positional deconstruction syntax to expand to an equivalent property deconstruction syntax that is ok, but permitting the compiler to do extra work implicitly that gets thrown away is bad.

@DavidArno I think it is somewhat unnecessary as well but the option type wasn't the point of that comment (merely a supporting framework for some ability to distinguish if the parameter was passed to the method or not); figuring out some way to do with expressions and a way to get rid of needing 2 methods to support positional deconstruction and with expressions was.

@DavidArno All tuples are ValueTuple<T,U>, etc provided by the BCL, but records are a concrete class/struct on their own.

@alrz,

I suspect F# has already answered this question for me, but if the tuple proposals are just syntactic sugar over the BCL Tuple<> types, how does the naming of the parameters work? Tuple<T,U> just results in Item1, Item2 etc, whereas in my example they are called real and imaginary. Wouldn't reflection show them up as the former?

@CyrusNajmabadi

Could you be more specific about why this makes it more of an issue?

method signature has hardly more then 10 params (rare), I estimate that average would be 3
class has easily 50 props, avarage would be domain-dependent - remember WPF clases ?

compare (a,b,c)
and (a,b,c,d,c,f,g,h,i,j,k)
the more items, the more mess can happen - n!

@DavidArno They will be erased during compile-time. Read the spec (#347).

As to needing to be baked in, for large scale interop yes; but the type could be used by the compiler as a well known type simply by being declared in the correct namespace.

@alrz,

My bad; thanks.

@JiriZidek I think you may then be arguing against a position i haven't made. I'd never argue that you should use positional in all cases. There are certainly cases (like the one you presented that i would agree would likely not be good candidates for positional).

It's the same argument i'd make for calling a method with 10 optional parameters when you only need to pass in a 2-3 values. Just used named parameters and make it nice and succint.

The value is that you have the choice and can choose whichever is best given the circumstances of the domain that you're currently in. For some domains nominal may be a much nicer and cleaner fit. For others positional may be great. But you get to make that choice :)

Pretty much any feature in C# today can be (ab)used for great or terrible things. We give a lot of credit toward developers being able to use these features sensibly in appropriate scenarios. I would never insist that we only have positional, or that positional is the right choice for all cases. But, at the same time, i would like to have it for the cases were it slots in very nicely.

@JiriZidek

compare (a,b,c)
and (a,b,c,d,c,f,g,h,i,j,k)
the more items, the more mess can happen

That's why we have named arguments for method invocation and property patterns. You still can use property patterns with records. It's just a matter of actually using it.

@alrz Yup! That's how i thnk it about it as well.

For construction/calling, we have nominal and positional. I think there's value in having both for deconstruction as well. Of course, it's up to the individual circumstance to decide which is best. But having both tools available seems very nice to cover the wide spectrum of scenarios out there, as well as the disparate opinions on what is the most readable/maintainable/consistent approach to take on a case by case basis.

@alrz

It's just a matter of actually using it

Not exactly. For classes named args flavor would be prevalent. Positioning would be rare - is it worth the effort then ?
I am just afraid you guys spending time on a feature, that would not be so used ?

@JiriZidek What effort? With record declaration you are defining properties in order, so the same order is used in positional deconstruction, still you can use property patterns to avoid this style of coding (that was actually what F# lacked at first and then they introduced named union fields).

@JiriZidek

It appears to be straightforward and perhaps necessary for tuples.

It appears to be straightforward and perhaps useful for records.

It seems to be an open question on the necessary complexity and usefulness for other types. Still it is very much worth at least evaluating what this might be.

@CyrusNajmabadi

I would never insist that we only have positional, or that positional is the right choice for all cases.

In fact having both posibilities would be great. But still I'd like to see at as opt-in and some more polished syntax without words.

@JiriZidek,

After following the debate, I'm coming down in favour of allowing positional decomposition, simply because it will be useful sometimes. The only reason though is due to the existence of the Roslyn analyzers.Taking an unrelated example, I view class inheritance and out parameters as an anti-pattern and code smell respectively. I can therefore use Roslyn analyzers to enforce my team's coding standards to treat the first as an error always and the second as an error, unless decorated with an exception attribute. @HaloFour and @alrz think my position on these topics, a couple of sandwiches short of a picnic; they'd never impose such restrictions, as is their choice.

So, if you dislike positional arguments, you too can enforce that for constructors, methods and decomposition with your own analyzers. Others are then free to use them wisely, or hang themselves with their own rope, as they choose fit.

So, if you dislike positional arguments, you too can enforce that for constructors, methods and decomposition with your own analyzers.

The language with the most unused features doesn't win, it makes the language more complex and requires more effort for new devs to learn - increasing learning/ramping up costs, devaluing its appeal to prospective devs that overall adds negative value to the language. Given language features can never be removed the onus should be for every feature to justify its existence where the value it provides should be measured against the complexity it adds to the language. Just because you dislike a feature doesn't mean you can just ignore it, its presence means it will be used (and potentially abused) and could be present in any source code that you're forced to work with.

Personally I don't see the value in Positional deconstruction as it's proposed, IMO equivalent examples using an "as alias" offer a superior more readable syntax that feels more natural to C#, is immediately intuitive to read and adds the least syntactical complexity to the language.

How would find all references work? I can check that for any constructor or method today. Would that work (since the constructor isn’t actually called, just used by the compiler to find a property).

@KathleenDollard

I haven't spec'ed out all the IDE work. I presume you're referring to _possible_ future where we do some sort of work to support positional deconstruction based on what your constructor shape is.

Now, in that hypothetical model of how we might do things, my initial intuition is that FindAllRefs on the constructor would find a match at the pattern deconstruction point. This fits my intuition because there would be a link (i.e. a reference) between these two entities. Indeed, if you refactored the constructor, we'd need to update positional patterns accordingly in order to keep your code working.

I see nothing immediately wrong or inappropriate with things working this way. Note though that until the feature is actually spec'ed out, any ideas about how the IDE work are of course purely speculative.

The language with the most unused features doesn't win.

I think fair counter examples of that are Javascript and Perl. Both languages have been highly influential for over 20 years with numerous features borrowed into other newer languages (there is a reason the acronym pcre exists after all) and widely used. I doubt anyone would argue either of them is not in contention for the prize "most unused features" either.

Still in this case arguing over whether or not positional decomposition should exist in C# is pointless. For unnamed tuples it seems to almost be a requirement for that feature to gain some usefulness in pattern matching scenarios. Expanding it to named tuples and records makes sense in the fact that it simplifies the language somewhat because there doesn't need to be some specific syntax that only works with tuples that happen to not be named and further pushes the concept that a record is conceptually a tuple with a name associated to it.

var notable = pointCloud.Where(t => t is (*, *, var notability, *) && notability != 0).ToArray();

tuple queries like that I think may be expected. If that works for anonymous tuples, it almost certainly should work for named tuples even if

var notable = pointCloud.Where(t => t.notability != 0).ToArray();

is by far better syntax. In keeping with that line of reasoning, enabling

var notable = pointCloud.Where(t => t is DataPoint(*, *, var notability, *) && notability != 0).ToArray();

where DataPoint is whatever this record type actually is looks to me to be conceptually the same thing only now it is restricting the enumerable to be actual DataPoint instances instead of merely things with 4 parameters accessible by position.

I think fair counter examples of that are Javascript and Perl.

Yep great languages reinforcing my point. Perl is famous for making incredibly terse and unreadable code, took 16 years to ship its latest Perl 6, its popularity has been declining for years where it's no longer on GitHub's top languages or a primary language choice among new Startups, nor has it been a strong influence for other languages - not sure what to take from Perl other than what not do to. The biggest programming language influencers have been LISP, Smalltalk and C which are all relatively small languages, with minimal but powerful language features.

JavaScript "the Good Parts" is a very small language with minimal features, its complexity derives from all the different nuances and corner cases one has to remember resulting from some poor language decisions it made early on. Those "nuances" take up conceptual space and are a rich source of bugs - I think it's fair to say JavaScript would be a much better and simpler language if its "bad features" never existed. At the same time I do enjoy using newer ES6/7 features which have made JavaScript more productive and readable. I think they've been tastefully added, improve readability, are instantly intuitive and are a great fit for the language.

Please let's avoid the language wars here.

@CyrusNajmabadi

The same thing exists today with any method/constructor. If you change the shape of it, you can easily break things. For example:

public Point(int x, int y)

Change those around, and you've broken every person who constructs a point. Constructors are part of your public API contract. If you change them, then you run the risk of breaking code. That would remain the same if the constructor was used for construction or deconstruction purposes.

So are we saying that the constructor defines the deconstruction shape? Interesting. So - would the constructor be one written by the user, or auto-generated? If the former - that at least is explicit so can be reasoned about more easily. But what if there are fields on the type not included in the constructor - e.g. calculated fields. And what if there are multiple constructors?

If the ctor is auto generated - this is where my concern is - then simply by changing the order of property definitions (or renaming them? Would they be positioned alphabetically?) you affect the pattern matching.

Another side point - you make an interesting point regarding giving users the freedom to choose what feature to use for e.g. pattern matching. So the developer can use either positional-style or explicit-named-fields style. There's another side to this coin though - the risk that there's not an idiomatic way to achieve this in C#. As it is, I suspect that the concept of pattern matching will be very, very new to many C# developers - might it be better to have a single, prescriptive way of achieving a given feature? I fear for new developers to the language seeing several ways to do the same thing.

@isaacabraham -
You have a great point about having multiple ways to do the same thing. I've heard many developers over the years actually complain about there being multiple patterns to do the same thing. What they want more than features is a consistent way to use those features.

@TonyValenti @isaacabraham

It's better for that to be a matter of style (enforced by code analyzers) or convention (encouraged by default behaviors). Even Python, with its one-right-way design principle is mostly a matter of style. Even little-endian arhcitectures uses big-endian memory storage. However, Haskell's enforcement of capitalized type names seems to work for _them_. It all comes down to culture. C# culture is based on C culture, which is _whatever works_. Consistency is good in general but a _foolish consistency_ isn't.

The only feature mentioned that seems remotely useful outside of "language" people is with. The other options, to me, add more syntactical rules to learn and yet provide no actual value. Why would I ever bother using the positional deconstruction approach (along with worrying about maintenance issues) when the standard constructor/object initializer syntax is cleaner, easier to read and more resilient to change. I really feel the language is trying to move over to a pattern/functional-based language and that is not what C# is about. I personally recommend all these features be left on the design table.

With makes sense but should behave like every other language. It is simply syntactical sugar around specifying a parent identifier. The common use case of treating it like an object initializer but without the new is reasonable. But it should also allow me to do anything that I could normally do by fully prefixing the members with the parent information.

@CoolDadTx I can say from personal experience that pattern matching and deconstruction in general are definitely very, very powerful feature and can save lots of boilerplate, especially around conditional branching statements (particularly when conditions stretch over multiple variables) - code is generally simpler to read and reason about. If whatever syntax can be thought up and settled upon fits nicely with existing C# syntax, I think it'll be well worth having in the language.

Two points:

First of all, let me just say that my initial example above was extremely simplistic, because the point wasn't to argue the value of the features, but to examine the range of possible mechanisms beneath them. I think that @CyrusNajmabadi and others brought up much more compelling examples of where you'd want positional deconstruction.

Secondly, there's a viewpoint inherent in the original records proposal (#206) that I gave short shrift in the original post above, but that I want to highlight as well: That we're not really talking about positional _deconstruction_ - as in grabbing the properties back out in some order - but positional _matching_, which is more powerful because you can define named "active patterns" that can match or fail based on complex logic, and pull out pertinent data in non-straightforward ways. In a sense, positional _deconstruction_ is just the simplest imaginable form of that, where you always match against the type itself, and always pull out values only of direct properties of the object.

Whether we want to embrace that extra power or not is an open design question. But it's part of the story of what positional patterns may be able to do for you.

@MadsTorgersen OK, if we're talking about something akin to F# active patterns that makes a lot more sense. I agree that they're definitely very powerful and help make a lot of complex matching logic sane.

@isaacabraham

So are we saying that the constructor defines the deconstruction shape? Interesting.

We are _considering_ (but have not even proposed or spec'ed out how it would work) a system whereby constructors could be taken into account whne using positional pattern matching.

The general intuition I have is:

  1. The type can fully describe its deconstruction shape (though some mechanism we haven't specified yet; possibly a method; possibly an operator). This takes priority if it exists.
  2. Externally from the type you should be able to describe its deconstruction shape (through some mechanism we haven't specified yet; possibly an extension method).
  3. If neither of the above exist, we _may_ try to infer the deconstruction shape from the constructor (through some mechanism we haven't specified yet).

This is the current state of things. I have been tasked with coming up with a proposal for '3', and i'm going to try to start on that next week. I have some ideas about what might work. But i'll have to experiment and run tests against large corpuses of C# code to see what sort of results we produce.

Thanks!

@isaacabraham

So - would the constructor be one written by the user, or auto-generated?

We have not investigated or spec'ed anything out yet. I have no idea :)

Another side point - you make an interesting point regarding giving users the freedom to choose what feature to use for e.g. pattern matching. So the developer can use either positional-style or explicit-named-fields style. There's another side to this coin though - the risk that there's not an idiomatic way to achieve this in C#.

All things being equal, i would be ok with that. But i'm ok with non-idiomatic when things are not equal. In my personal opinion there is enough value in brevity and symmetry to warrant positional deconstruction (plus it's practically required for tuples). As such, i think we'd want them. But this will not be a decision made in isolation. We will treat it as seriously as we do all language changes.

As it is, I suspect that the concept of pattern matching will be very, very new to many C# developers - might it be better to have a single, prescriptive way of achieving a given feature? I fear for new developers to the language seeing several ways to do the same thing.

That's definitely a reasonable concern. Though, one that i'm not sure i'm that concerned about here. We have, in the past, introduced _very_ new things for C# devs with multiple ways to use them. For example, when we introduced linq we heard the same things about the method form .Where(...).Select(...) vs the query form where ... select .... But in practice it turned out fine and people mix and match the different facilities where it makes sense to them.

@TonyValenti

What they want more than features is a consistent way to use those features.

Consistency is in the eye of the beholder :)

To me deconstruction, is the parallel of construction. As such, it's _inconsistent_ that i can construct something some way, but then have to deconstruct it some other way :)

@CyrusNajmabadi Thanks for the detailed answers. And yes, I know this is a very early stage of design - and it's great that you and the rest of the team are pro-actively getting feedback at this stage too. I don't expect you to have all the answers just yet - but questions like this from myself and others are probably useful to you all the same :-)

And of course LINQ is a great example of that point. Do you use a for loop? Lambda syntax? Query syntax? etc. - and yes, I've been there and done that on many real-world projects with large teams, having to come up with internal standards for what to use, and in what circumstances. It's a constant challenge as the language grows new features - some people have a tendency to adopt new feature everywhere simply because they're new; others shy away from there; others use them inconsistently etc. etc..

So yes, freedom of choice is all well and good - but sometimes, just sometimes, developers want to be told what to do (even if they don't admit it!).

To me deconstruction, is the parallel of construction. As such, it's inconsistent that i can construct something some way, but then have to deconstruct it some other way :)

Wouldn't that only apply to "dumb" constructors i.e. that only set properties that map 1:1 with constructor arguments? In which case you can get by with no constructor and setting the properties in an object intializer. How's it going to work when there's no relationship between constructor arguments and the types stored fields?

it's inconsistent that i can construct something some way, but not deconstruct it another way

Using type invocation for records; that just makes sense,

// Person is a record
var person = Person("Foo", "Bar");
if(person is Person("Foo", "Bar"))

But for regular classes that you use new,

// Person is not a record
var person = new Person("Foo", "Bar");
if(person is new Person("Foo", "Bar")) // ????

This is what F# is doing, it requires new just for regular classes (§6.4.2):

The type ty must not be an array, record, union or tuple type

So it is reasonable to not be able to deconstruct them in the same way.

In which case you can get by with no constructor and setting the properties in an object intializer.

I personally try to use immutable objects as much as possible. So, unfortunately, object initializes are off the table for me.

So it is reasonable to not be able to deconstruct them in the same way

In your eyes, sure. In mine no. I can positionally construct something. I would like to be able to positionally deconstruct something.

Anyways, i'm not sure we're making any progress here. The feedback has been heard. There is a lot of pushback against positional deconstruction/matching in certain circumstances. At this point, i'm not sure debating any further is adding any new information to the discussion. Perhaps we should table this?

So yes, freedom of choice is all well and good - but sometimes, just sometimes, developers want to be told what to do (even if they don't admit it!).

I'm really hesitant to go there. The language has thrived so far making judicious choices about allowing people multiple ways to do things. Linq is a great example. When i talk to many C# developers it's literally at the top of their list for things they love about C#. Turns out that flexibility and non-presciprtive attitude turned out to be a huge benefit, even if it means we don't have total consistency over the entirety of C# code out there. It turned out there to be great not having a 'one size fits all' attitude. As you yourself mentioned, you've see all sorts of different developers who have each gravitated to the different options we made available. This does not seem to have been a negative for linq. Indeed, it seems to be one of its strengths, and why it was so widely accepted and adopted.

@CyrusNajmabadi I meant if we use is new to hint the compiler that it's ok to use those sort of matching of parameter and property names, I'd be ok with that. That is truly symmetrical construction and deconstruction.

@alrz

Potentially, though i can see issues with that. We've always used 'new' to indicate that you're constructing. So in this specific case you would not want the 'new' because you would explicitly _not_ be constructing. Indeed, as you'd be doing the opposite, there's a strong argument to be made that 'new' could be considered very inappropriate here.

Note that the symmetry i was referring to was in positionally passing data into the object as i construct it, vs positionally extracting that data back out again. As that was something you could do with constructors, it's something i would like with de-constructors. (man, i keep wanting to say 'destructors', and that's just not right ;-) ).

I think there's some really nice consistency here if we go that route: "Node(...)" tells you the type and the positional information you need. When you use "new" you're saying "now construct me one of these" and if you leave off "new" you're saying "now deconstruct this guy".

Note: no syntax is set in stone. We'll definitely want to evaluate a bunch of options here.

@CyrusNajmabadi Yes, but I'm thinking that another keyword should do, because is Node(..) in case of a non-record type is way different than simple matching of the type and "positional properties", it matches the "names" not positions (yes positions are significant in the pattern itself but eventually the compiler depends on names), so in my opinion it deserves its own keyword because semantics are different. PS: I don't have a strong disagreement here because symmetric construction and deconstruction is definitely a good thing, just to suggest some alternatives, that's it.

One thing that worries me when I'm reading this is performance. In particular, runtime performance of those features.
Linq, as a good example, is already a no go feature in high performance code, because while it is extremely readable, the cost of delegate invocations and lambda allocation can be extremely high.

Looking at the proposed work the compiler has to do to support those features, I'm afraid that this would turn into another feature that high perf code cannot touch.

The fact that this is compiler generated code doesn't matter for the costs we are going to have to pay.

@ayende Could you be more specific. What work in particular do you think would be an issue?

@CyrusNajmabadi

But i don't want to write it as a record. I want to write it as a class

I thought about this and now I think I understand why. There are types that are not records really but happen to have some immutable properties which are initialized in the constructor by parameters. For example,

public class Person {
  public string FirstName { get; }
  public string LastName { get; }
  public string MiddleName { get; }
  public Person(string firstName, string lastName) {
    this.FirstName = firstName;
    this.LastName = lastName;
    // presumably not possible with records
    // and perhaps not appropriate for a record
    // because record's constructor is intended to be 'dumb'
    // it just gets parameters and sets properties, one by one
    this.MiddleName = GetMiddleName(); 
  }
 }

And of course, it totally makes sense to have a symmetrical construction and deconstruction for this type.

The solution proposed here is dealing with two issues:

  1. The convention difference between parameter and property names which leads us to case insensitivity.
  2. It assumes that each parameter (hopefully) initializes a property with the same name, modulo case. This is a big assumption, not that one might actually do it, but if a code is not correct, it _should not be possible_ to write it I believe, and when I say that, I mean it should produce a compile-time error. It would be a shame if the compiler itself does depend on such assumptions.

I've proposed some kind of parameters (#9234) that might address both of these issues. For example,

public class Person {
  public string FirstName { get; }
  public string LastName { get; }
  public string MiddleName { get; }
  // equivalent to the constructor above 
  public Person(this.FirstName, this.LastName) {
    this.MiddleName = GetMiddleName(); 
  }
 }

This is somehow similar to the feature that facilitates With method,

abstract Person With(string firstName = this.FirstName, string lastName = this.LastName);

But in constructors we don't need an optional argument, because the property itself is being initialized, so we just write it _instead_ of the parameter, and with no doubt, the first parameter here will be assigned to the FirstName property and so on.

Same can be helpful for implementing GetValues or is operator.

public void GetValues(out this.FirstName, out this.MiddleName, out this.LastName);

And in this case, the compiler doesn't even need to actually call this method. It is merely a shape for a custom positional deconstruction (besides of the constructor itself). So,

if(person is Person("Mickey", var middleName, *)) {}
// equivalent to
if(person.FirstName == "Mickey") {
  var middleName = person.MiddleName;
}

No additional out parameter passing and no assumptions.

I like deconstruction but I feel as though the property names themselves
should be specified. I believe this would leave the code much less brittle
and a lot easier to understand.

Why not do something like this?

if(person is Person(FirstName = "Mickey", var mname = MiddleName)) {

}

On Sun, Mar 6, 2016 at 12:29 AM, Alireza Habibi [email protected]
wrote:

@CyrusNajmabadi https://github.com/CyrusNajmabadi

But i don't want to write it as a record. I want to write it as a class

I thought about this and now I think I understand why. There are types
that are not records really but happen to have some immutable properties
which are initialized in the constructor by parameters. For example,

public class Person {
public string FirstName { get; }
public string LastName { get; }
public string MiddleName { get; }
public Person(string firstName, string LastName) {
this.FirstName = firstName;
this.LastName = lastName;
// presumely not possible with records
// and perhaps not appropriate for a record
// because record's constructor is intended to be 'dumb'
// it just gets parameters and sets properties, one by one
this.MiddleName = GetMiddleName();
}
}

And of course, it totally makes sense to have a symmetrical construction
and deconstruction for this type.

The solution proposed here is dealing with two issues:

  1. The convention difference between parameter and property names
    which leads us to case insensitivity.
  2. It assumes that each parameter (hopefully) initializes a property
    with the same name, modulo case. This is a big assumption, not that one
    might actually do it, but if a code is not correct, it _should not be
    possible_ to write it I belive, and when I say that, I mean it should
    produce a compile-time error. It would be a shame if the compiler itself
    does depend on such assumptions.

I've proposed some kind of parameters (#9234
https://github.com/dotnet/roslyn/issues/9234) that might address both
of these issues. I'll explain.

public class Person {
public string FirstName { get; }
public string LastName { get; }
public string MiddleName { get; }
// equivalent to the constructor above
public Person(this.FirstName, this.LastName) {
this.MiddleName = GetMiddleName();
}
}

This is somehow similar to the feature that facilitates With method,

abstract Person With(string firstName = this.FirstName, string lastName =
this.LastName);

But in constructors we don't need an optional argument, because the
property itself is being initialized, so we just write it _instead_ of
the parameter, and with no doubt, the first parameter here will be assigned
to the FirstName property and so on.

Same can be helpful for implementing GetValues or is operator.

public void GetValues(out this.FirstName, out this.MiddleName, out this.LastName);

And in this case, the compiler doesn't even need to actually call this
method. It is merely a shape for a custom positional deconstruction
(besides of the constructor itself). So,

if(person is Person("Mickey", var middleName, *)) {}
// equivalent to
if(person.FirstName == "Mickey") {
var middleName = person.MiddleName;
}

No additional out parameter passing and no assumptions.


Reply to this email directly or view it on GitHub
https://github.com/dotnet/roslyn/issues/9330#issuecomment-192813040.

Tony Valenti

I wrote my primary constructor proposal in #9517. The idea is to avoid trying to deduce a deconstructor from a constructor if the constructor is not primary. This way positional deconstruction will work only in those cases where it is likely to be used.

@CyrusNajmabadi,

There is a lot of pushback against positional deconstruction/matching in certain circumstances.

There is a lot of pushback from very few. It might be the case that a large mass of people agree with your view and think you're defending your points in very reasonable way. I do!

@CyrusNajmabadi I agree that positional deconstruction is a good and useful feature, as long as it's opted into by implementing a custom deconstructor or a primary constructor.

I like the proposed positional deconstruction syntax. I'm guessing the folks objecting to it or finding it confusing aren't familiar with pattern matching in other languages. But adding pattern matching to C# is very high on a lot of people's wish lists (including mine). A great thing about C# has been the willingness to add awesome features of other languages even if it means people had to learn some new syntax - e.g., lambdas.

I'm not opposed to it either, I think it should be implicit for records and tuples and explicitly opted in via extension methods or something like that for other types. Further I think it should result in basically the exact same IL for deconstruction that property patterns do (there should be 0 runtime cost using this form vs property patterns).

@bbarry

I'm not opposed to it either, I think it should be implicit for records and tuples and explicitly opted in via extension methods or something like that for other types. Further I think it should result in basically the exact same IL for deconstruction that property patterns do (there should be 0 runtime cost using this form vs property patterns).

Isn't that self-contradicting? If decomposition is enabled by an extension method it would have to invoke different IL than a pattern property, namely a static method call, which would then proceed to populate it's out variables (or tuple return value) in any way that it saw fit.

Just addressing the casing issue: isn't the best of both worlds available? Property names should still be PascalCased and parameter names should still be camelCased. To match, the parameter name should be required to be identical to the property name but with the first character changed to lowercase.

No case-insensitivity necessary. I agree that it would be an unwanted cost to add insensitivity here.

Not necessarily. A GetValues() method isn't needed at all beyond the fact that it has a signature that provides a way to impose order on the relevant properties. This order could be derived for example from the With() method as I described above https://github.com/dotnet/roslyn/issues/9330#issuecomment-191834692

I threw together a little crappy microbenchmark comparing some different decomposition strategies. Mileage may vary but here's the numbers it spits out for me:

| Benchmark | Debug 32-bit | Debug 64-bit | Release 32-bit | Release 64-bit |
| --- | --- | --- | --- | --- |
| Properties | 8.63 | 8.76 | 1.01 | 0.51 |
| Instance out | 22.37 | 21.36 | 1.51 | 0.50 |
| Instance tuple | 50.60 | 52.75 | 4.53 | 10.57 |
| Static out | 27.05 | 27.02 | 1.83 | 0.50 |
| Static tuple | 1:04.14 | 1:13.82 | 18.65 | 21.17 |

Not terribly surprising. The JIT likely is successfully inlining both the property accessors and the out methods in those simple cases.

Here's the benchmark, feel free to tear it apart:

https://gist.github.com/HaloFour/d2ca5c056ba1139245f0

@bbarry What is the benefit of that over the argument names of the constructor? Seems that it's still voodoo, it's just been moved to a different place and still requires new syntax with new grammatic rules beyond being a simple extension method. How would those special with methods be represented by normal IL/C#? Attributes?

Constructors that currently exist for types aren't necessarily one to one with properties on the type. I don't think anything special is necessary in applying that contextual keyword to the method signature other than applying those semantic rules to the method.

That said I am not sure that specific syntax is a great idea; only that it is a possible way to achieve the functionality.

It isn't crystal clear to me whether With(...) and GetValues(...) are methods that people need to implement or it's just methods that the compiler generates, can someone please clarify that for me?

@eyalsk You can implement them yourself if you need them, records will implement them for you, the open question is whether any other classes should get them for free or not.

@orthoxerox thank you for the clarification. :)

Design notes have been archived at https://github.com/dotnet/roslyn/blob/future/docs/designNotes/2016-02-29%20C%23%20Design%20Meeting.md but discussion can continue here.

Was this page helpful?
0 / 5 - 0 ratings