Open MadsTorgersen opened 7 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.
@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.
@nietras The idea is "Yes, but not now".
@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?
@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.
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.
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?
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.
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?@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. :)
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.
- 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.
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 calledFoo
which returns an instance of some type calledrecord
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.
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.
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.
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.
@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.
@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.
@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.
@Joe4evr where have you seen that? Are you talking about nominal or positional records?
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. :)
Please don't name it data class
please just introduce record
keyword or something short to type.
@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?
No. A positional record would not have a parameterless constructor unless it had no record values (ie a unit type).
@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
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.
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.
@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.
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 class
es 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.
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;
}
}
Instead of using
data class
, why not just usereadonly class
? So that it does not introduce any new keyword and remain consistency with other parts of language.
Cant agree any more.
give back to me C#7.2 please!!!
@xgstation Because readonly struct
already exists and means something different.
@huoxuxu
<LangVersion>7.2</LangVersion>
@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.
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 .
I'm having trouble finding that part of the discussion, what would be different between a proposed data struct
and readonly struct
?
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.
sigh And what's the difference between a record type and an immutable type other than available language features?
A data struct
is a subset of readonly struct
which allows with
expressions. (Assuming F#'s mutable record fields are not implemented.)
argee with it
@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?
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 struct
s but data struct
is a convenient syntactic sugar over them)
@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.
with
expressions only depend on methods named With
on the target type, not whether the target type is a record.
@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)
Hmm. Seems that the blog post is inconsistent with the proposal linked in the top comment. Maybe the proposal is out of date.
@lcsondes Then yes, the protected constructor should be the only dependency of the with
expression.
@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.
@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?
@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.
LDM notes: