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

Comments

@gafter
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

This comment has been minimized.

Show comment
Hide comment
@ghord

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

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

This comment has been minimized.

Show comment
Hide comment
@ztone

ztone 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

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

This comment has been minimized.

Show comment
Hide comment
@casperOne

casperOne Feb 3, 2015

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

casperOne commented Feb 3, 2015

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

@SolalPirelli

This comment has been minimized.

Show comment
Hide comment
@SolalPirelli

SolalPirelli Feb 3, 2015

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.

SolalPirelli commented Feb 3, 2015

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

This comment has been minimized.

Show comment
Hide comment
@mpawelski

mpawelski Feb 3, 2015

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.

mpawelski commented Feb 3, 2015

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

This comment has been minimized.

Show comment
Hide comment
@MgSam

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

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

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Feb 4, 2015

Member

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);
Member

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

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Feb 4, 2015

Member

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.

Member

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

This comment has been minimized.

Show comment
Hide comment
@MrJul

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

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

This comment has been minimized.

Show comment
Hide comment
@dsaf

dsaf 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?

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

This comment has been minimized.

Show comment
Hide comment
@MgSam

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

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

This comment has been minimized.

Show comment
Hide comment
@Miista

Miista Feb 4, 2015

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

Miista commented Feb 4, 2015

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

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban 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).

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

This comment has been minimized.

Show comment
Hide comment
@axel-habermaier

axel-habermaier Feb 5, 2015

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.

Contributor

axel-habermaier commented Feb 5, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban 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).

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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

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

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

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban 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 notifications@github.com 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 -

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 notifications@github.com 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

This comment has been minimized.

Show comment
Hide comment
@axel-habermaier

axel-habermaier Feb 5, 2015

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.

Contributor

axel-habermaier commented Feb 5, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Feb 5, 2015

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

HaloFour commented Feb 5, 2015

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

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Feb 5, 2015

Member

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

Member

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

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban 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; }
}

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

This comment has been minimized.

Show comment
Hide comment
@axel-habermaier

axel-habermaier Feb 5, 2015

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

Contributor

axel-habermaier commented Feb 5, 2015

@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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

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

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.

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 17, 2016

Member

@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)) ...

Member

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

This comment has been minimized.

Show comment
Hide comment
@bbarry

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

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

This comment has been minimized.

Show comment
Hide comment
@gordanr

gordanr 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 !=

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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 25, 2016

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 commented Mar 25, 2016

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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 25, 2016

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

HaloFour commented Mar 25, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 25, 2016

Member

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

Member

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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 25, 2016

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

👍

HaloFour commented Mar 25, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@gordanr

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

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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 25, 2016

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

HaloFour commented Mar 25, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@gordanr

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

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

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 25, 2016

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

HaloFour commented Mar 25, 2016

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

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Mar 25, 2016

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
Contributor

orthoxerox commented Mar 25, 2016

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

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Mar 25, 2016

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.

Contributor

orthoxerox commented Mar 25, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Mar 25, 2016

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 commented Mar 25, 2016

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

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Mar 25, 2016

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?

DavidArno commented Mar 25, 2016

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

This comment has been minimized.

Show comment
Hide comment
@Joe4evr

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

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

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Mar 26, 2016

Contributor

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

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 28, 2016

Member

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.

Member

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