dotnet / csharplang

The official repo for the design of the C# programming language
11.61k stars 1.03k forks source link

Champion "Records" (VS 16.8, .NET 5) #39

Open MadsTorgersen opened 7 years ago

MadsTorgersen commented 7 years ago

LDM notes:

nietras commented 4 years ago

@MadsTorgersen been reading this but still confused whether or not the following is actually possible:

public data struct Point2D<T>(T X, T Y);

That is defining a value-type data struct with this.

HaloFour commented 4 years ago

@CoolDadTx

The design is still very much in the air, as is the name. Per the latest meeting notes the suggested syntax might be:

record class Person(string FirstName, string LastName)
// or
record class Person { string FirstName; string LastName }

It's certainly possible for this to change (again). At this point it's not worth bike shedding over the name of the feature or the exact keywords to be used. Lots of features change syntax and name drastically over the course of design.

Joe4evr commented 4 years ago

@nietras The idea is "Yes, but not now".

CoolDadTx commented 4 years ago

@HaloFour, exactly but when you put out a feature you're expecting feedback on the design. Is that not the purpose of this Github issue? If we aren't supposed to provide feedback on language features here then where is the appropriate place to do that? If we cannot provide feedback then there is no way for the design to be vetted before release. So where does feedback like this belong?

HaloFour commented 4 years ago

@CoolDadTx

Very good point. 😄

This particular proposal has been all over the place in terms of design lately, so I wouldn't get too hung up on the particular syntax. There have been big design swings in just the past week or two, including which keywords have been suggested. You could consider "records" as being the blanket term of even code name of the proposal.

Hermholtz commented 4 years ago

I oppose the choice of data class and propose record keyword. You call the feature "record" but use another moniker. This doesn't make sense.

wygoodin commented 4 years ago

The Welcome to C# 9.0 blog post has this paragraph in the discussion of with and inheritance:

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

This surprised me. Given the natural concerns with copying subclasses, why is a copy constructor being used for the copy operation at all? It seems reasonable to have an ordinary Copy virtual method default implementation, similar to the other default method implementations for records. Are we trying to avoid adding default methods beyond the ones that come from the Object superclass?

CyrusNajmabadi commented 4 years ago

I oppose the choice of data class and propose record keyword. You call the feature "record" but use another moniker. This doesn't make sense.

  1. We don't have a name for the feature yet. We have general terms which may or may not bubble down to the language with specific syntax.
  2. record on its own has syntactic ambiguity challenges. for example: public record Foo(int a, int b) { }. Is that the declaration of a new record? Or is it a method called Foo which returns an instance of some type called record in the user codebase?
HaloFour commented 4 years ago

@CyrusNajmabadi

Sounds like another good reason to have record be a modifier of class or struct rather than a separate type in it's own right. :)

CyrusNajmabadi commented 4 years ago

Sounds like another good reason to have record be a modifier of class or struct rather than a separate type in it's own right. :)

Sure. And that's a possibility we're considering. Nothing has been finalized here.

Hermholtz commented 4 years ago
  1. We don't have a name for the feature yet. We have general terms which may or may not bubble down to the language with specific syntax.

That's good.

  1. record on its own has syntactic ambiguity challenges. for example: public record Foo(int a, int b) { }. Is that the declaration of a new record? Or is it a method called Foo which returns an instance of some type called record in the user codebase?

Aren't some keywords 'contextual' in C# such as value?

I'm not the expert, but wouldn't the following work?

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

As for public record in user codebase I conducted (very limited) GitHub search - as it doesn't allow exact case-sensitive string search - and did not spot this issue on the first 20 pages of results. This is not an evidence it won't occur, but IMHO the probability is next to zero.

CyrusNajmabadi commented 4 years ago

Aren't some keywords 'contextual' in C# such as value?

'value' isn't contextual. it's just an identifier.

I'm not the expert, but wouldn't the following work?

That looks like a class only with a primary constructor. Not necessarily a record.

but IMHO the probability is next to zero.

It very likely much higher than that. There are codebased out there that use lowercase names for this sort of thing for all sorts of reasons (interop being a common one). We cannot take that code and suddenly break it. We have a very high back compat bar, and having one syntactic option that breaks versus anotehr that we think is fine, but which does not break, will almost certainly lead to the latter being chosen.

CyrusNajmabadi commented 4 years ago

Finally: please don't get hung up on syntax. It's one hte last things to be decided on. We make strawmen all the time so we have things to try out. We want to understand and experiment with the concepts first. Then we can decide how we like the syntax to be at the end with all that info.

mkane91301 commented 4 years ago

Forgive me if this covers old ground, but...

I'd like to express my opinion regarding the syntax for initializing records. Let's take for example the following record: public data class Person(string FirstName, string LastName) { string MiddleName }

This means, if I understand correctly, that FirstName and LastName are required and MiddleName is optional. As far as I understand, currently only the following syntax is supported for creating a record of this type: var person = new Person("John", "Jingleheimer-Schmidt") { MiddleName = "Jacob" };

I would also like to see the following allowed: var person = new Person("John", "Jingleheimer-Schmidt", MiddleName = "Jacob"); // like attributes var person = new Person(FirstName = "John", MiddleName="Jacob", LastName = "Jingleheimer-Schmidt"); // like other cases of named parameters var person = new Person { FirstName = "John", MiddleName = "Jacob", LastName = "Jingleheimer-Schmidt" }; // making constructor parameters appear to be property initializers

I would expect a compiler error if a required parameter were missing in any syntax: var error = new Person(FirstName = "John", MiddleName = "Jacob"); // LastName required var error = new Person { FirstName = "John", MiddleName = "Jacob" }; // LastName required

Fundamentally, I don't want to force my code to use a particular initialization style just because I'm using the constructor syntax to force certain properties to be required.

Joe4evr commented 4 years ago

@mkane91301 The syntax is going to be the third one you have there, but not for the reasons you think.

// making constructor parameters appear to be property initializers

When it comes to records, those aren't constructor parameters. They're properties with an init accessor which allows assigning values to them in an object initializer. They also don't indicate required-ness, see the discussion in #2328.

mkane91301 commented 4 years ago

@Joe4evr Let me clarify. When I was speaking about required, I didn’t mean requiring it to be non-null, only requiring that it be explicitly initialized and not taking the default initialization, because I was only talking about the case where there is no default constructor. And after thinking about this, I realized that my point was not only about records with no default constructor but applies equally to all classes without default constructors.

Joe4evr commented 4 years ago

@mkane91301 Well, any class without a parameter-less constructor will behave just the same as today, but I believe the design is that record classes will always generate a parameter-less constructor, so you have to account for not receiving any arguments at all.

YairHalberstadt commented 4 years ago

@Joe4evr where have you seen that? Are you talking about nominal or positional records?

nietras commented 4 years ago

good reason to have record be a modifier of class or struct rather than a separate type in it's own right. :)

@HaloFour I concur. The number one use case for us for this feature is for value types. Not classes. Even considering value tuples. Having simple named types is just better for this. A data/record struct is ideal for that. I therefore hope value types will be prioritized too. :)

bugproof commented 4 years ago

Please don't name it data class please just introduce record keyword or something short to type.

mkane91301 commented 4 years ago

@Joe4evr where have you seen that? Are you talking about nominal or positional records?

I don’t know about Joe, but my comment was specifically about positional records. I got the impression that they do not have parameterless constructors. Did I misunderstand?

333fred commented 4 years ago

No. A positional record would not have a parameterless constructor unless it had no record values (ie a unit type).

HaloFour commented 4 years ago

@333fred

With the latest meeting notes and direction on records is it expected to be able to mix "positional" and "nomimal" record behavior? Or does that fall under the question as to whether positional records would allow for additional members?

333fred commented 4 years ago

@333fred

With the latest meeting notes and direction on records is it expected to be able to mix "positional" and "nomimal" record behavior? Or does that fall under the question as to whether positional records would allow for additional members?

I don't expect it would be blocked. We've determined that the equality will be value equality for all fields, so I expect they would "just compose" TM.

mkane91301 commented 4 years ago

I don't expect it would be blocked. We've determined that the equality will be value equality for all fields, so I expect they would "just compose" TM.

Based on this, it appears that my example with positional FirstName and LastName and nominal MiddleName is valid. So, in this case, I’d like to see the options I presented for initializing such a record and not be restricted the way we are now for a class with the same shape.

HaloFour commented 4 years ago

@mkane91301

Assignment expressions are already legal syntax to pass to a parameter in C#, so that syntax could not be reused in this case. C# uses : for named parameters instead.

Trying to mix constructors with initializers has been discussed before (I can't find where) and I think that the team didn't like the idea because it implies that the order of the parameters and members does not matter when they do. Reordering properties would not break existing consumers, but reordering constructor parameters definitely will. There's also the question of resolving which constructor to call. Records (like any types) can have multiple constructors, and trying to ascertain which constructor to used based on trying to interpret any number of members which could be interpreted to be either a member or a parameter would very likely be confusing to both the developer and to the compiler.

lcsondes commented 4 years ago

I've just read the blog post on C# 9 and the one thing that stood out to me as really scary is that in data classes what looks like a field and what would be a field in every other version of C# is an auto-property. If that syntax remains as described on the blog I really hope there will be an .editorconfig (or equivalent) setting to require people to write one of the locally-unambiguous forms (that is: visibility accessor that makes it a field, property accessors that mark clearly that it's a property, or the primary constructor (record constructor? I don't know how it's called in C#). Changing int Thing; to internal int Thing; or public int Thing; as part of some code cleanup (even the default accessibility is different!) would be a binary-incompatible change. This is really sneaky and requires a level of language-lawyering that I was happy to leave with C++. If a change like this ever gets committed to a large class everyone now has to remember during code review that they need to scroll to the very top to check what kind of type this is because chances are that won't be in the diff context.

gen-xu commented 4 years ago

Instead of using data class, why not just use readonly class? So that it does not introduce any new keyword and remain consistency with other parts of language. For example,

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

is equivalent to

public class Person
{
    public readonly string FirstName;
    public readonly string LastName;
    public Person(string firstName, string lastName)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
    }
}
zsh2401 commented 4 years ago

Instead of using data class, why not just use readonly class? So that it does not introduce any new keyword and remain consistency with other parts of language.

Cant agree any more.

huoxuxu commented 4 years ago

give back to me C#7.2 please!!!

svick commented 4 years ago

@xgstation Because readonly struct already exists and means something different.

Happypig375 commented 4 years ago

@huoxuxu

<LangVersion>7.2</LangVersion>
lcsondes commented 4 years ago

@svick readonly class doesn't exist though. readonly struct is pretty similar to a record, it just needs unlocking a few features like init accessors and with to bring it up to parity.

spydacarnage commented 4 years ago

I thought that at one point, it was discussed that the "data" prefix could be applied to structs as well as classes, which could be why a new prefix was used.

To use "readonly" would redefine previous code and would thus potentially be a breaking change.

On Sun, 24 May 2020, 10:06 lcsondes, notifications@github.com wrote:

@svick https://github.com/svick readonly class doesn't exist though. readonly struct is pretty similar to a record, it just needs unlocking a few features like init accessors and with to bring it up to parity.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/dotnet/csharplang/issues/39#issuecomment-633201481, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADIEDQPJN3TKOV7DF4TLW4DRTDPPVANCNFSM4C7SB5ZA .

lcsondes commented 4 years ago

I'm having trouble finding that part of the discussion, what would be different between a proposed data struct and readonly struct?

Happypig375 commented 4 years ago

A data struct is a record type with copy-by-value semantics while a readonly struct is an immutable type with copy-by-value semantics.

lcsondes commented 4 years ago

sigh And what's the difference between a record type and an immutable type other than available language features?

Happypig375 commented 4 years ago

A data struct is a subset of readonly struct which allows with expressions. (Assuming F#'s mutable record fields are not implemented.)

mi1890 commented 4 years ago

argee with it

lcsondes commented 4 years ago

@Happypig375 And what prevents with expressions, init property accessors, etc. of which there are currently 0 in shipped-C# code being allowed for readonly structs?

Happypig375 commented 4 years ago

Nothing prevents with expressions, init property accessors, etc. of which there are currently 0 in shipped-C# code being allowed for readonly structs. (They will be implemented for all structs but data struct is a convenient syntactic sugar over them)

lcsondes commented 4 years ago

@Happypig375 We got there in the end, thanks.

It would be pretty annoying if, e.g., I wanted to work with an immutable struct from a library and do in essence a with but I couldn't do it just because it's a readonly struct, not a data struct that are considered different in this scenario.

Happypig375 commented 4 years ago

with expressions only depend on methods named With on the target type, not whether the target type is a record.

lcsondes commented 4 years ago

@Happypig375 this blog post seems to imply that with calls into a constructor overload that takes an object of your own type as an argument (it might make more sense to have it be YourType(in YourType) in the case of structs–which is not covered there–since you're already copying it)

Happypig375 commented 4 years ago

Hmm. Seems that the blog post is inconsistent with the proposal linked in the top comment. Maybe the proposal is out of date.

Happypig375 commented 4 years ago

@lcsondes Then yes, the protected constructor should be the only dependency of the with expression.

HaloFour commented 4 years ago

@Happypig375 @lcsondes

That blog post is also out of date and inconsistent with very recent meeting notes and design review sessions. I'd take it as more an idea of what the team is trying to achieve, not the final syntax or behavior of the feature.

https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-04.md https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-11.md

For the record (har har pun), the latest branch that contains record syntax uses a Clone method, not a copy constructor. It then assigns the initializer directly over the returned instance, which, for a positional record, involves writing directly to the private readonly fields.

lcsondes commented 4 years ago

@HaloFour Thanks. Could I please ask you to chime in on the separation between readonly struct and a potential data struct that's being mentioned here? First of all, is this even a thing? If yes how are they intended to differ?

HaloFour commented 4 years ago

@lcsondes

The readonly keyword on a struct does nothing more than enforce that the members of the struct can't mutate that instance of the struct. It doesn't bring all of the rest of the opinions that come with being a "record", although those exact opinions are still being ironed out. Most of those opinions apply more to references since structs already have value identity. I don't see why that would preclude record struct, or even readonly record struct, but we'll have to wait and see. Implementing them only for classes now doesn't prevent them from supporting structs in the future, as long as they keep the class keyword as a part of the declaration.

Happypig375 commented 4 years ago

@HaloFour In fact, readonly data struct is already possible.