Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Pattern matching and record types #206

Closed
gafter opened this issue Feb 3, 2015 · 258 comments
Closed

Proposal: Pattern matching and record types #206

gafter opened this issue Feb 3, 2015 · 258 comments

Comments

@gafter
Copy link
Member

gafter commented Feb 3, 2015

The spec has been moved

The specs for pattern matching and records have been moved to https://github.com/dotnet/roslyn/blob/features/patterns/docs/features/patterns.md and https://github.com/dotnet/roslyn/blob/features/records/docs/features/records.md

There are new discussion threads at #10153 for pattern-matching and #10154 for records.

@ghord
Copy link

ghord commented Feb 3, 2015

Overall, great proposal.
Just a minor nitpcik: * seems a little out of place in language. I propose to simply use var instead by omitting variable name:

case Mult(var, Const(0)): return Const(0);
case Mult(Const(1), var x): return Simplify(x);

If we would allow to match by only type name and skip variable name, one could even perform simple type test cases like this:

bool IsInteger(Expr e) 
{
    switch(e) 
    {

        case Const(int): return true;
        case Const(double val): return IsInteger(val);
        case Const(var): return false;
        ...
    }
}

I think it would be more consistent behaviour.

@ztone
Copy link

ztone commented Feb 3, 2015

F# record (for reference)

type Cartesian = { X : double; Y : double }
let c = { X = 1.0; Y = 2.0 }

This is similar valid C# syntax

class Cartesian { public double X; public double Y; }
var c = new Cartesian { X = 1.0, Y = 2.0 };

Proposal. This is a example of a simple syntax transition to a c# record from the code above. The record is an immutable class with just default public readonly fields. Records could use the expected immutable class implementation.

record Cartesian { double X; double Y; }
var c = new Cartesian { X = 1.0, Y = 2.0 };

var ok = c.X; // ok
c.X = 3.0; // Error, a readonly field cannot be assigned to.

Another option could be (although I would prefer the above)

record Cartesian { double X; double Y; }
var c = new Cartesian(X: 1.0, Y: 2.0);

var ok = c.X; // ok
c.X = 3.0; // Error, a readonly field cannot be assigned to.

Just my 2 cents

@casperOne
Copy link

Why would IEquatable<T> not be implemented? You're overriding Equals(object) already, so IEquatable<T> should be a no-brainer.

@SolalPirelli
Copy link

I may be reading the spec wrong, but it doesn't look like it's specifying the return type of operator is. Shouldn't it force it to be bool? The == and != operators lack such a constraint, but IMHO there is no valid use for non-bool-returning equality operators.

@mpawelski
Copy link

Were you considering to use _ character instead of * for wildcard pattern? Every language I can think of use this character for that.
I sometimes use this character as lambda argument to indicate that this parameter won't be used. I know that _ is a valid name for variables and classes and you can write something like this (_, other) => _.PropertyAccess but maybe there is a way to use this character and don't break existing code? For example previous code would compile (maybe with warning) and this one would fail (_, _) => _.PropertyAccess because _ would be treated as a wilcard character.

And when it comes to pattern matching we could disallow to name variable as _ (for examle this pattern Cartesian(var x, var _) won't compile) and allow _ to be wilcard character. It's a new feature so old code won't be broken.

I just thing that _ is so prevalent in other languages that it is worth considering to use it instead of *. For someone familiar with pattern matching from other language it would be instantaneously clear what the code do when they'll see C# code with pattern matching and with this character. I think similarity with other languages is nice to have.

@MgSam
Copy link

MgSam commented Feb 4, 2015

Great to see the updated proposal!

Some comments:

  • The example shows auto-implementing of ToString, but the spec says only that Equals and GetHashCode will be implemented.
  • I think auto-implementing of the == and != operators is a no-brainer here. IEquatable I don't feel strongly about as I never see types that actually use it.
  • I like the record keyword. It seems weird that the mere existence of a primary constructor would initiate all this other magic behavior as well. Doing so may lead non-primary constructor classes to become second class citizens (as auto-implementation of GetHashCode, Equals, and ToString will do the right thing in 95% of cases and thus be highly desirable). IMO, the magic associated with records seems orthogonal to primary constructors.
  • I really hope the design committee looks at the with operator. Immutability/records needs this operator to be useful without a ton of extra boilerplate for copying immutable types. As others have mentioned previously, Roslyn code would also benefit greatly from a with operator.
  • The biggest value adds for the code I write every day are records (mostly as proposed) + with operator. The pattern matching stuff is just nice to have gravy.

@gafter
Copy link
Member Author

gafter commented Feb 4, 2015

These record types can easily support 'with' expressions like

Point p = ...;
p = p with { X: 4 };

which would generate

p = new Point(4, p.Y);

@gafter
Copy link
Member Author

gafter commented Feb 4, 2015

The == and != operators would only be a good idea for record structs. It is too easy to get in trouble with them on reference types.

@MrJul
Copy link
Contributor

MrJul commented Feb 4, 2015

@MgSam IEquatable<T> is often used implicitly, for example in a Dictionary<TKey,TValue> or a HashSet<T> with the default comparer. EqualityComparer<T>.Default uses IEquatable<T> if it's available, or falls back to Object.Equals if it doesn't, meaning a perf/memory hit for value types due to boxing.

@dsaf
Copy link

dsaf commented Feb 4, 2015

Is it me, or 'record' is a completely unintuitive name for the concept?

http://en.wikipedia.org/wiki/Record_%28computer_science%29

"...which are types whose semantic meaning is described by the shape of the data..." - who would say, "oh, thats records"?

Why not call them 'algebraic' or something else that is actually trying to describe the concept?

@MgSam
Copy link

MgSam commented Feb 4, 2015

@gafter But with is not in the proposal yet, correct?

@gafter Could you elaborate as to how == and != are a problem with reference types? These operators typically delegate their behavior to Object.Equals, so I don't see how the problem changes at all. If someone wants a reference comparison they would be foolish to assume == does that and not use Object.ReferenceEquals.

@Miista
Copy link

Miista commented Feb 4, 2015

@dsaf Yes, I totally agree. Especially since a record type is an algebraic data type.

@Richiban
Copy link

Richiban commented Feb 5, 2015

I propose declaring record classes as such:

public record class Person
{
    string Name { get; set; }
    DateTime DoB { get; }

    bool IsRobot { get; set; } = false;
}

Several things to note:

  • the lack of visibility modifiers. Like an interface, everything is public in a record class
  • Members can be mutable or immutable. Both types need to be intialised when instantiating the record type
  • Members can be optional. This is signified by giving a default value in the type definition.

The advantage of this is that records syntax is the same as the class syntax in C# 6. The record keyword just tells you that the constructor, equals, ToString and GetHashCode methods are implemented for you.

I also feel that, when initialising a record, the object initialisation syntax is better than constructor syntax (it plays a lot better with the concept of optional members).

@axel-habermaier
Copy link
Contributor

@Richiban: Why no accessibility modifiers? Even F# allows that (sort of). Also, I agree that this syntax is better than the proposed one using primary constructors (especially the capital/noncapital parameter name thing is just plain ugly), however, primary constructors allow for code to be executed. That way, you can, for instance, validate the data stored in the type (Name != null, in your example). F# doesn't have that either, and it's something that is missing in certain cases.

@Richiban
Copy link

Richiban commented Feb 5, 2015

@axel-habermaier I guess that my gut feeling was that members of a record that differ in accessibility from the type itself don't make much sense; with private members it stops feeling like a record. Protected doesn't apply because records are not inherited (I assume).

@HaloFour
Copy link

HaloFour commented Feb 5, 2015

How exactly do immutable members work without a constructor? Is the compiler supposed to infer and generate constructors based on usage? That would make it impossible to expose the type publically. Or would a constructor be generated including all of the members with the optional members either implemented as optional parameters or via overloads? That would make adding a new member, even an optional one, a breaking change.

@Richiban
Copy link

Richiban commented Feb 5, 2015

Yes, the compiler generates the constructor for you from the members you
define in your class. Optional members are not present in the constructor,
they are assigned to after instantiation just like object initialisers in
previous versions of C#.

On Thu, Feb 5, 2015 at 5:19 PM, HaloFour [email protected] wrote:

How exactly do immutable members work without a constructor? Is the
compiler supposed to infer and generate constructors based on usage? That
would make it impossible to expose the type publically. Or would a
constructor be generated including all of the members with the optional
members either implemented as optional parameters or via overloads? That
would make adding a new member, even an optional one, a breaking change.


Reply to this email directly or view it on GitHub
#206 (comment).

  • Richard Gibson -

@axel-habermaier
Copy link
Contributor

@Richiban: F# allows you to make all members private, for instance. That way, you can create "opaque" records that you can pass around safely to outside code, but that no outside code can modify. That has its uses.

Concerning inheritance, I think that should be possible as well to add a mechanism like F#'s discriminated unions or Scala's case classes to C#. @gafter provided an example in 8.4 where record types inherit from an abstract base class. Scala seems to support case classes that are derived from other case classes. Interestingly, Scala's case class concept allows for more type safety than F#'s discriminated unions.

In any case, I think there are two separate issues: 1) Are there any technical reasons preventing records to be derived from other records? 2) If record inheritance is allowed, in what situations would that be useful?

I don't see anything on the technical side that would stop the compiler from supporting record inheritance. Under the hoods, record classes are compiled down to regular classes, which of course support inheritance. Auto-generated members like Equals or ToString would have to invoke the base behavior or completely override the base behavior. Similar for the new is operator. The primary constructor could just forward the values of the inherited members to the primary constructor of the base type.

As for the use case, I have Roslyn's tree of SyntaxNodes in mind. That is a somewhat complex hierarchy of types that define the syntactic structure of C# code (= concrete syntax tree). Without record inheritance, it would not be so elegantly possible to express the tree. For instance, there's a MemberDeclarationSyntax base class from which concrete declaration syntaxes for constructors, properties, etc. are derived. That way, the TypeDeclarationSyntax can have a Members property of type MemberDeclarationSyntax, preventing you from accidentally adding an expression as a member to a type. Without record inheritance, you'd loose a great deal of type safety here.

@HaloFour
Copy link

HaloFour commented Feb 5, 2015

@Richiban That would make it impossible to have a member be both optional and immutable/readonly.

@gafter
Copy link
Member Author

gafter commented Feb 5, 2015

@axel-habermaier It was not the intent to restrict inheritance in this specification. Of course that would only work for classes, as structs are implicitly sealed.

@Richiban
Copy link

Richiban commented Feb 5, 2015

@HaloFour "That would make it impossible to have a member be both optional and immutable/readonly." Yes, because an immutable but optional member doesn't make much sense in my mind...

I've written out my proposal in C# 5 to make it clear what would be possible:

public record class Person
{
    string Name { get; set; }
    DateTime DoB { get; }

    bool IsRobot { get; set; } = false;
}

is the equivalent of

public class Person
{
    public Person(string name, DateTime dob)
    {
        Name = name;
        _DoB = dob;
        IsRobot = false;
    }

    string Name { get; set; }

    private readonly DateTime _DoB;
    DateTime DoB { get { return _DoB; } }

    bool IsRobot { get; set; }
}

@axel-habermaier
Copy link
Contributor

@HaloFour: I don't see how either approach avoids the problem of breaking changes whenever you add/remove a member from a record type. F# has the same problem.

Just tossing some ideas around.... The only thing I can think of that would somewhat alleviate the issue is the following, though that requires you to manually consider binary compatibility (then again: that's always the case. You also can't add a new constructor parameter to a class that has already shipped. Even adding a new constructor might be breaking change when reflection is taken into account).

record class R
{
   public int X;
   public int Y;
}

The compiler would generate the following constructor:

public R(int x, int y) { X = x; Y = y; }

You'd be able to initialize the record either by invoking the constructor like new R(1, 2); or using an object initialize like new R { X = 1, Y = 2 }; that would be mapped to a constructor call by the compiler.

What happens if you omit a member, as in new R { X = 1 }? That could either be an error, or the compiler could generate new R(x: 1, y: default(int)). I'm not sure. Alternatively, you could say that only a declaration like

record class R
{
   public int X;
   public int Y = 4;
}

allows Y to be omitted during the creation of an instance, such that new R { X = 1 } maps to new R(x: 1, y = 4);.

What happens when you add a new member? That would of course change the signature of the constructor, making it binary incompatible (although, if the new member is optional, simply recompiling the assembly would fix the issue). To solve this problem, we could allow explicit constructor declarations in records like the following:

record class R
{
   public int X;
   public int Y;
   public R(int x, int y) { if (x < 0) throw ...; X = x; Y = y; }
}

In that case, the compiler would not generate a constructor, since you already explicitly provided one. This has two interesting consequences: 1) It allows you to do parameter validation if needed. 2) It allows maintaining binary compatibility if a member is added. Say we add a new member Z and a new constructor overload as follows:

record class R
{
   public int X;
   public int Y;
   public int Z;
   public R(int x, int y) { X = x; Y = y; }
   public R(int x, int y, int z) { X = x; Y = y; Z = z; }
}

Code using R does not have to be recompiled, although reflection scenarios might break. The new member could be optional or not, all that matters is how you write your constructors.

Note how I'm trying to avoid using primary constructors here. I still don't like the proposed syntax. My proposal, however, is admittedly less concise for the simple cases. Let's see how we'd write the type using the proposed primary constructor syntax. The original definition would be:

class R(int x : X, int y : Y);

or with the parameter validation from above:

class R(int x : X, int y : Y)
{ 
   {
      if (x < 0) throw ...
   }
}

Significantly shorter. But: x : X is just plain ugly. The primary constructor body syntax is just ugly. How do you specify separate attributes for the constructor parameters and the properties/fields? Easier/more obvious in my proposal. Now what happens if we want to add Z, maintaining binary compatibility?

class R(int x : X, int y : Y, int z : Z) 
{
   {
      if (x < 0) throw ...
   }
   public R(int x, int y) : this(x, y, 0) { }
}

At that point, at least in my opinion, primary constructors have lost all their value...

@HaloFour
Copy link

HaloFour commented Feb 5, 2015

@Richiban Immutable and optional doesn't seem that uncommon to me. It's a pattern that I've made use of in the past.

Also, as mentioned, by generating the constructor you've effectively made it impossible to ever add another immutable member since that would require changing the constructor signature.

@nkosi23
Copy link

nkosi23 commented Mar 15, 2016

I like this proposal quite a lot, especially the record feature, but it makes me nervous. People managed to create Big Balls of Mud in the enterprise by painfully maintaining these records by hand to create anemic domain models. Now these people will think : "If they provide first-class support fort that, I must be right". I'm pretty sure that the first use case of this feature won't be data transfer objects to move data between tiers, but big balls of mud.

But this comes back to Lippert's remark : "At some point the developper needs to take responsibility".

Just wanted to voice my concern so that the record shows that you're clean and you won't be sent to hell on judgement day.

@vbcodec
Copy link

vbcodec commented Mar 17, 2016

Does pattern matching work for anonymous types or while asking for existence of certaine fields / properties ?

For example, I ask for existence width and height in object::

if (c is (int width, int height)(var R, *)) Console.WriteLine(R);

or

if (expr is (int width, int height) v) { // code using v, v is interpreted as tuple}

@gafter
Copy link
Member Author

gafter commented Mar 17, 2016

@vbcodec We have not yet specified pattern-matching support that would work for anonymous types. For tuples, we expect you to be able to write

if (o is (int x, int y)) ...

@bbarry
Copy link

bbarry commented Mar 17, 2016

Conceptually I think omitting the type name for a property pattern jives well:

if (c is { width is int v }) Console.WriteLine(v);

(would match any type with a property width that is an int; including anon types)

Not sure if this is worth doing though...

@gordanr
Copy link

gordanr commented Mar 25, 2016

I've just read recently added 'Draft feature specification for records'.
https://github.com/dotnet/roslyn/blob/future/docs/features/records.md

This draft suits all my needs. Congratulations for the excellent work.
Usefulness of the records could be dramatically increased in the future with non-nullable reference types.

I suggest that you add a small example in this draft with members explicitly declared in a class-body.

public sealed class Student(string Name, decimal Gpa)
{
   public string ToString() => $"Name:{Name}: Gpa:{Gpa}";
}

I would also like that this draft provides a small example with explicit constructor. Sometimes developers need some sort of validation or transformation in constructor.

public sealed class Student(string Name, decimal Gpa)
{
   public Student(string Name, decimal Gpa)
   {
        if (Gpa < 0) throw new ArgumentException("Gpa < 0", nameof(Gpa));
        this.Name = Name;
        this.Gpa = Gpa;
   }
} 

Do we need operators in translated code?
public static bool operator ==
public static bool operator !=

@HaloFour
Copy link

Nice, didn't notice that records finally got its own spec.

Since the type implements IEquatable<T> I think that it is worthwhile that it has == and != operators auto-generated. Also I think a default ToString override to list the type name and the property values would be nice, something like Student(Name: "Bill", Gpa: 3.5).

Some random comments/questions:

For caller-receiver-parameters, how is the new mapping of the argument to an instance property/field encoded? Attribute?

public int Foo { get; set; }
public void Bar(int foo = this.Foo) { }
// sorta translated into:
public void Bar([MemberDefault("Foo")] int foo) { }

Will this work with static methods and static properties/classes as well?

public static class StaticClass {
    public static int Foo { get; set; }
    public static void Bar(int foo = StaticClass.Foo) { }
}

If the new default property value can be used in normal method calls I would say that the with expression isn't really that useful. var bar = foo with { X = 123 }; doesn't buy you much over var bar = foo.With(X: 123); except having to learn a new syntax. Method invocation would be better suited to chaining expressions. An argument could be that the with syntax helps to illustrate its purpose since it is similar to an initializer.

Having to repeat the primary constructor in order to add logic to initialization seems a little ... repetitive. I definitely prefer something more constructor-like to provide scope rather than having code directly in the record body. But perhaps some kind of shorter syntax would be appreciated (and cause less of a conniption among the DRY crowd.)

🍝

// Java-like initializer body
public class Student(string Name, double Gpa) {
    {
        this.Name = Name;
        this.Gpa = Gpa;
    }
}

// Constructor without arguments
public class Student(string Name, double Gpa) {
    public Student {
        this.Name = Name;
        this.Gpa = Gpa;
    }
}

Has syntax for specifying a parameter name that differs from the property name been scrapped?

For the is operators is the reason that they're void because the operand will be compared to the record type first and then if the operand is compatible it will then resolve and call the operator to decompose?

With inheritance still on the table has there been any thoughts or solutions to handling the public Square(int Width) : Rectangle(Width, Width); problem?

@HaloFour
Copy link

@bbarry

Conceptually I think omitting the type name for a property pattern jives well:

Do you mean in specific contexts where the type of the operand is known and the compiler can confirm has that property?

var obj1 = new Rectangle(10, 10);
var obj2 = new { Width = 20 };
object obj3 = obj2;

if (obj1 is { Width is int width }) { ... }
if (obj2 is { Width is int width }) { ... }
if (obj3 is { Width is int width }) { ... } // compile-time error?

Otherwise I don't see how the compiler could manage that pattern without some nasty reflection.

@gafter
Copy link
Member Author

gafter commented Mar 25, 2016

@HaloFour 👍

We have not specified ToString for records, nor operator == and operator !=. It is an open question whether or not we will do anything for them.

We do not currently have a specified mapping to metadata for caller-receiver-parameters. We do not intend to extend that to static properties/fields.

We're aware that the with expression may not be needed. That's the reason for the "open issue" in that section of the spec.

I like the idea of public Student { ... } to add code to the primary constructor.

We are actively discussing what to do about naming. I expect the possibility to name the parameters and properties differently will reappear in the final spec.

We have solved the Square:Rectangle problem. The solution appearing in the draft specification is not the one we are going to end up with, but this margin is too small to hold the details. Stay tuned for more details.

@HaloFour
Copy link

@gafter

We're aware that the with expression may not be needed. That's the reason for the "open issue" in that section of the spec.

Nod, and why I mentioned it. I agree with the thinking that it's probably unnecessary.

I like the idea of public Student { ... } to add code to the primary constructor.

Also gives you an easy syntax for giving the constructor itself a different accessibility modifier without having to introduce anything weird in the record declaration itself:

public class Student(string Name, double Gpa) {
    internal Student {
        this.Name = Name;
        this.Gpa = Gpa;
    }
}

I don't know if the syntax seems too property-esque, tho.

The solution appearing in the draft specification is not the one we are going to end up with, but this margin is too small to hold the details. Stay tuned for more details.

👍

@gordanr
Copy link

gordanr commented Mar 25, 2016

Often we can use pure records and that is really great.
But sometimes we need some checks for safe construction of records.
That's why I would like to see an explicit constructor example.

public sealed class SomeClass(record-parameter-list)
{
    public SomeClass(record-parameter-list)
    {
        var validationCode = ValidationCode(record-parameter-list);
        if (validationCode != "OK")
        {
            throw new Exception(validationCode);
        }
        ...initialize backing field with record-parameter-list. 
    }

    public static string ValidationCode(record-parameter-list)
    {
       // Returns some message if something is wrong with record-parameter-list 
       // or "OK" if all class invariants are satisfied.
    }
}

Most of the validation code is checking for nulls. That could be resolved with non-nullable reference types.
Other validations are mainly relative simple checks that could be also resolved with method contracts.

If we in the future had support for non-nullable reference types and method contracts, we could use pure records (without body) for complex Domain-driven design cases.

@HaloFour
Copy link

@gordanr

So spit-balling with #119:

public class Student(string Name, double Gpa)
    requires Name != null else throw new ArgumentNullException(nameof(Name))
    requires Gpa > 0.0 else throw new ArgumentException(nameof(Gpa)); 

Or with #8181:

public class Student(string Name, double Gpa) {
    guard (Name != null) else throw new ArgumentNullException(nameof(Name));
    guard (Gpa > 0.0) else throw new ArgumentException(nameof(Gpa));
}

In either case the contract could be applied to both the constructor and the generated With method.

@gordanr
Copy link

gordanr commented Mar 25, 2016

Or even simpler with help of non-nullable reference types.

public class Student(string Name, double Gpa)
    requires Gpa > 0.0 else throw new ArgumentException(nameof(Gpa));
}

That would be really awesome. Meanwhile we can only use explicit constructor.

@HaloFour
Copy link

@gordanr Nod, the enforcement maybe as a last resort, just in case someone squeaks a null through anyway.

@orthoxerox
Copy link
Contributor

Looks like the spec is missing the changes to the class-base part of the declaration:

If there are arguments in the class-base specification, a base constructor selected by overload resolution with these arguments is invoked;

In the current c# spec, class-base is:

class-base:
    :   class-type
    :   interface-type-list
    :   class-type   ,   interface-type-list

@orthoxerox
Copy link
Contributor

@gafter

We have not specified ToString for records, nor operator == and operator !=. It is an open question whether or not we will do anything for them.

I'm not sure if a default ToString will be useful beyond debugging output. Maybe a default DebuggerDisplayAttribute will be more useful?

I like the idea of public Student { ... } to add code to the primary constructor.

+1 to that idea

We have solved the Square:Rectangle problem. The solution appearing in the draft specification is not the one we are going to end up with, but this margin is too small to hold the details. Stay tuned for more details.

I'm excited. Current spec forces you to use only abstract rectangles, if I'm reading it correctly.

@DavidArno
Copy link

Regarding the open questions/undecided stuff:

Please implement == & != for records. I can't really see a case where one would not want == implemented exactly the same as .Equals() as shown in the examples. Not implementing them will therefore force everyone to either manually define them for every record (sort of defeating one of the point of records, to cut down on boiler-plate, or to force exclusive use of.Equals when handling records.

The with expression is a useful syntactic-sugar addition. Considering @HaloFour's example, what will really happen without this is peoples will write var bar = foo.With(123);, dropping the optional X:. Much better to provide a language feature that guides the developer into writing clearer code: var bar = foo with { X = 123 };. This offers a massive improvement in readability for very little compiler cost.

@DavidArno
Copy link

In the examples, both a sealed and abstract class record are shown. But the spec shows class-modifiers as optional, suggesting class records can be declared that are neither. What would be the point of them and what would the resultant syntax look like?

@Joe4evr
Copy link

Joe4evr commented Mar 26, 2016

what will really happen without this is peoples will write var bar = foo.With(123);

This can be mitigated by shipping an analyzer+codefix in the box. In a case like this, I'd rather MS provide one rather than every Roslyn analyzer author implementing their own.

@alrz
Copy link
Member

alrz commented Mar 26, 2016

@gafter I do like to see with syntactic sugar for the sake of CamelCase property names. For the same reason I don't think a dedicated syntax to name the parameters and properties differently would be deadly useful unless you want to follow the naming convention when you write named arguments in the constructor. That said, I think the ability to use object initializer syntax for record construction can provide this code style consistency, specially when you have default values for properties and want to omit some of them short of their order using named arguments (Note that with with this is always the case).

As for @HaloFour's suggestion, to not look like a property, how about something similar to C++ syntax?

public sealed class Student(string Name, double Gpa) {
  // no parameters permitted
  default private Student() {
    // additional logic, if any
  }
  // still you can define a ctor without parameter
  private Student() : this("Batman", -1) {}
  public static Student Create() { ... }
}

Although the primary constructor should be accessible publicly for deconstruction short of its accessibility.

@gafter
Copy link
Member Author

gafter commented Mar 28, 2016

The specs for pattern matching and records have been moved to https://github.com/dotnet/roslyn/blob/features/patterns/docs/features/patterns.md and https://github.com/dotnet/roslyn/blob/features/records/docs/features/records.md

There are new discussion threads at #10153 for pattern-matching and #10154 for records.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests