dotnet / csharplang

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

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

Open MadsTorgersen opened 7 years ago

MadsTorgersen commented 7 years ago

LDM notes:

hickford commented 6 years ago

Record types are invaluable for domain modelling. I use them all the time in F# https://fsharpforfunandprofit.com/posts/records/

I look forward to record types in a future version of C#

bugproof commented 5 years ago

Would there be a way to make records mutable? I know they're immutable by default to work more functionally like in F# and to be used in conjunction with with

darcythomas commented 5 years ago

@fragilethoughts My understanding is record types are immutable by design, but a .With()method is always generated; so you can can create a new updated copy whenever you want a "mutated" version.

gafter commented 5 years ago

Would there be a way to make records mutable?

If you want any property to have a setter as well as a getter, you just declare that property (or a field). The user-declared property then takes the place of the one that would be compiler-generated.

carlreinke commented 5 years ago

If you want any property to have a setter as well as a getter, you just declare that property (or a field). The user-declared property then takes the place of the one that would be compiler-generated.

Would you also be able to do this for the constructor to, for example, add argument validation logic? If so, hopefully the programmer doesn't make a mistake and change the order of the parameters! Then you've got a constructor that validates and one that doesn't.

Presumably, to add documentation comments to properties/fields, you'd have to re-declare them all in the record body. Hopefully you don't make a typo in the names!

I'd love to have the compiler take a lot of the work out of creating immutable types with With methods, but this proposed design seems error-prone.

gafter commented 5 years ago

Would you also be able to do this for the constructor to, for example, add argument validation logic?

No, but I expect we’ll have a syntax for adding code to the ctor.

orthoxerox commented 5 years ago

I'm learning Rust right now, and it uses attributes to automatically generate its local equivalents of ToString, GetHashCode, IEquatable, IComparable, ICloneable. Basically, what @louthy does, but the other way round.

I wonder if using a similar approach for records would be sensible.

notanaverageman commented 5 years ago

How about auto code generation using #107 and attributes? There is already a working case here.

A few advantages I can think of:

For example it is much easier to call a user given function before setting the properties than creating a new syntax just for this:

No, but I expect we’ll have a syntax for adding code to the ctor.

HaloFour commented 5 years ago

@yusuf-gunaydin

Code generation is currently up in the air due to the complexities of integrating it with the tooling. If that project can overcome those hurdles I'd probably agree that much of what you can accomplish with records could be accomplished with code generation. But I think that there's benefits in having the idiomatic approach adopted in the language, specifically with how it would interact with other language features like pattern matching. This is especially true if records form the basis for discriminated unions.

ghost commented 5 years ago

Record types are by default immutable, the With method provides a way of creating a new instance that is the same as an existing instance but with selected properties given new values

This comes with a cost of reassigning all properties just to change one of them! Can't we use the readonly class keyword to make the record immutable, so the records without the readonly can be mutable? Also, we can use the readonly keyword in declaring individual properties so we can have mixed read/write and readonly properties. This will make each one design his record to fit his needs with max efficiency. And this will not change anything about the with method and syntax.

CyrusNajmabadi commented 5 years ago

This comes with a cost of reassigning all properties just to change one of them!

Why is that a problem? Roslyn, for example, is built on these principles and this hasn't been an area that's been an issue at all.

ghost commented 5 years ago

@CyrusNajmabadi This may not have a heavy impact on Roslyn, but a large record instances being modified in large loop can have a heavy impact on efficiency.

CyrusNajmabadi commented 5 years ago

This may not have a heavy impact on Roslyn, but a large record instances being modified in large loop can have a heavy impact on efficiency.

Doctor, it hurts when i do that.

Richiban commented 5 years ago

@MohammadHamdyGhanem while I would never suggest something as draconian as making mutable records impossible, I would strongly insist that they be immutable by default. Generally speaking any type that implements equals and GetHashCode should really be immutable.

popcatalin81 commented 5 years ago

Generally speaking any type that implements equals and GetHashCode should really be immutable.

True, but why do records need to implement Equals? What's the use of equals on Records? Records should be lightweight (immutable) classes. Lightweight classes do not generally implement equals unless structural equality is needed. I don't think for a large number of use cases structural equality is needed (For structs generally yes, for classes generally no).

Take a hypothetical person Record example:

public class Person(int Id, string Name, DateTime birthDate);

Even if the record is imutable the hash code should be based solly on the Id field, that's the only trully identifying field of the logical person record.

theunrepentantgeek commented 5 years ago

Lightweight classes do not generally implement equals unless structural equality is needed.

I've found that my lightweight types need to implement Equals() and GetHashCode() very frequently indeed - any time you want to use one as a dictionary key, or put it into a set, or persist it with an ORM, etc etc.

My experience is that types that don't need to implement Equals() and GetHashCode() are the exception, not the rule.

Richiban commented 5 years ago

True, but why do records need to implement Equals

That's the entire motivation behind this feature. A record is a type that has the boilerplate of a constructor, Equals, GetHashCode and (probably) ToString generated for you.

popcatalin81 commented 5 years ago

True, but why do records need to implement Equals

That's the entire motivation behind this feature. A record is a type that has the boilerplate of a constructor, Equals, GetHashCode and (probably) ToString generated for you.

I'm sorry but that's not what I was asking.

The motivation to have Equals and GetHashCode for a struct record like point is obvious. It's not as obvious to me (and that's why I was asking where is this useful) to have Equals and GetHashCode for reference type records like a Person record.

GetHashcode is used when the object is used as key in a hashmap. Makes sense to use a point structure as key, IE:

   public struct Point(int X, int Y);
   public struct Color(int X, int Y);
   ...
   var colors = new Dictionary<Point, Color>()

However doesn't make much sense to reference type records as hashmap keys.

   public class Person(int Id, string Name, DateTime? birthDate);
   ...
   var peopleDepartments = new Dictionary<Person, Department>();
   ...
   // later
   var person = person.With(birthDate: new DateTime(1990, 1,1));
   peopleDepartments[person].Name // Oups !!!

Yes you would not use a record as key in this case. But in what case would you?

popcatalin81 commented 5 years ago

Anyway, this is more of an exercise to understand records, because not giving an immutable class structural equality is even worse.

wanton7 commented 5 years ago

@popcatalin81 in your example case why would you ever use Person object as dictionary key instead of Id?

popcatalin81 commented 5 years ago

@wanton7 It is very common to have the ID be data store generated, which means there are lots of use cases where entities not saved to the durable store to have the id as default clr type, in this case 0. And you could have more than one record in this state.

But I think we are back to the discussion, what's the semantic of Equals, identity equality or structural equality. I sure wish they would be separated in .Net framework at this point.

wanton7 commented 5 years ago

@popcatalin81 In your previous post you wrote.

Even if the record is imutable the hash code should be based solly on the Id field, that's the only trully identifying field of the logical person record.

If you would be using Equals just for Id field , then you would have same exact problem as using Id directly when it's 0. Using container like dictionary might not be the best choice when you have needs like that.

popcatalin81 commented 5 years ago

@wanton7 Yes, same problem, this has always been the problem with implementing GetHashCode : the object's identity. With mutable .Net instances you get a free unique hash code. With Immutable types this is not true any more.

Richiban commented 5 years ago

@popcatalin81 To me, from a domain modelling point of view, there's no reason to split the concept of structural equality between types that are passed by copy and those that are passed by reference.

I rarely use structs in my modelling; they're reserved (since you can't hide the default constructor) strictly for domain types that have the concept of zero.

To contrast, I have an enormous number of types that look something like this:

public class Username
{
    private readonly string _value;

    public Username(string value)
    {
        if (value == null) ...
        if (value.Length < 3) ...
        if (value.Length > 25) ...

        _value = value;
    }

    public int GetHashCode() => ...

    public override bool Equals(object other) => ...
    public bool Equals(Username other) => ...

    public override string ToString() => ...
}
popcatalin81 commented 5 years ago

@Richiban Records will not help with your codding pattern from your example. They do not allow building constrained domain objects. They are only lightweight immutable POCO classes.

So it's not clear what benefit are you trying to show from your example.

Richiban commented 5 years ago

@popcatalin81 Perhaps I've missed something, but this is exactly what Records will fix:

[Record]
public class Username(string Value)
{
    {
        if (Value == null) ...
        if (Value.Length < 3) ...
        if (Value.Length > 25) ...
    }
}

(Note, I've invented the syntax here, because we still don't know what it will look like).

Don't let the fact that my first example only had one property be a red herring; this is just as relevant:

[Record]
public class PersonName(string FirstName, string Surname)
{
    {
        if (FirstName == null) ...
        if (FirstName.Length < 2) ...

        if (Surname == null) ...
        if (Surname.Length < 2) ...
    }
}
MarkusAmshove commented 5 years ago

Would the withers actually copy data of unchanged values or would it be able for the compiler to do some magic in referencing the same fields, if a reference is cheaper than copying the value?

andre-ss6 commented 4 years ago

I'm sorry if this is answered elsewhere, but I couldn't find anything about it. Will there be generic records?

gafter commented 4 years ago

@andre-ss6

The draft syntax at https://github.com/dotnet/csharplang/blob/master/proposals/records.md#record-type-declarations includes a type parameter list in a record declaration.

andre-ss6 commented 4 years ago

Oh, of course! That looks like a promising place to check! Hahaha. Thanks.

Right, so my actual question is actually another: will there be support for covariance? Seeing that, as far as I'm aware, records are expanded to a normal class/struct declaration, then I guess it won't. However, is there any story on this? Is that something that is being discussed? Or would that be another issue altogether?

The reason I ask here is that I've been experimenting for some time now in one of our projects with generic domain models and DTOs - and just using more "language features" in general, as opposed to how I historically tried to keep domain models as POCOs as possible (which sometimes introduced lots of duplicate code) - and the need for covariance quickly arised. The issue is that, if I want to completely reap the benefits of having a generic domain model class, all of a sudden I need to start defining domain model interfaces, in addition to classes, and using them as parameters in my repositories. And that, to me, feels like going too far. And let's not even talk about interface DTOs. And then records are perfect for domain models: they don't have any logic associated with them, are immutable and are free of boilerplate.

rotemdan commented 4 years ago

Hi, I appreciate the work being done to get this important feature into the language (a feature which I personally anticipate).

I've read most of the of the proposal so far. The more I read the more I started to feel the current approach seems to be overly focused on achieving a goal of apparent simplicity (ideal) and appeal to functional idioms, at the expense of flexibility (real-world) and consistency with the existing style of the language:

Functional-like syntax doesn't scale nicely and doesn't provide significant benefit over class syntax

public class RotationAngles(float X, float Y, float Z);

This seems pretty terse and I like the fact it can be written in one line (though it's still only a type declaration, which I feel extreme brevity is not a high priority for). One issue I see is that the syntax doesn't immediately suggest customizability, and would not scale well to records having a large number of parameters or ones including lengthy initializers (which I'm assuming would be allowed?).

Feels like we've "downgraded" back from a nicely extensible class syntax to awkward parameter lists, where delimiters don't align nicely and future modifiers (like protected/private etc.) would look a bit out of place):

public class MyComplexRecord(
    SomeLongClassName<float[]> SomePropertyWithLongName = new SomeLongClassName<float[]>,
    AnotherLongClassName<int[]> AnotherPropertyWithLongName = new AnotherLongClassName<int[]>
    /* and so on .. */
);

Confusing duality between function and class-like syntax

Say we wanted to customize the default operations provided, say, to override the Equals method. With current syntax it would look like:

public class RotationAngles(float X, float Y, float Z)
{
    public override bool Equals(RotationAngles other)
    {
        return NormalizeAngle(X) == NormalizeAngle(other.X) &&
               NormalizeAngle(Y) == NormalizeAngle(other.Y) &&
               NormalizeAngle(Z) == NormalizeAngle(other.Z)
    }
}

I see several aspects that seem unintuitive about this:

  1. Since the enclosing scope is presented like a normal public class .. it may seem to users that they can just add new (even mutable?) members to the body of the class like so (I could not understand from the proposal if that would be allowed?):
public class RotationAngles(float X, float Y, float Z)
{
    public float newMutableMember; // why not?

    public override bool Equals(RotationAngles other)
    {
        ..
    }
}
  1. It's not intuitively obvious that one can define a plain constructor since the brackets notation seem to suggest the possibility of a predefined one:
public class RotationAngles(float X, float Y, float Z)
{
    public RotationAngles(float X, float Y, float Z) // Hmm.. can I do that??
    {
        ..
    }
}
  1. Constructor inheritance seems awkward and verbose (especially for a feature that aims for terseness), e.g.:
public class RotationAngles(float X, float Y, float Z)
{
    public RotationAngles(float X, float Y) : RotationAngles(X, Y, 0f) // Paainnful..
    {
    }
}

I'd rather use initializers for default values instead (which I'm still unsure if would be allowed or not), if are allowed, they would be inconsistent with the general constraint on method default arguments to be compile-time constants (it would seem arbitrary to constrain them in this way only for the sake of consistency, as well).

public class RotationAngles(float X, float Y, float Z = 0f)
  1. There's no clear indication the members defined through the primary constructor are read-only (apart from the fact that are enclosed as a method-like parameter sequence attached to the class declaration, which by itself doesn't intuitively signify anything about mutability).

Pattern matching and the proposed with operator

For the pattern matching style suggested:

p is Point(3, var y)

I don't personally find the fixed ordering rendering it any simpler or readable than say:

p is Point { x: 3, var y }

Which also nicely extends to regular classes and structs.

The with syntax proposed:

p with { x = 5, ... }

Doesn't match well with the function-like syntax, e.g.:

new Point(x = 3) with { y = 5, ... }

Applying it to standard initializer syntax would look more natural:

new Point { x = 3 } with { y = 5, ... }

Suggested alternative

I'd suggest reconsidering a more conventional syntax (though with several deviations from normal classes/structs), which I feel is more consistent with existing constructs and style of the language (however note that all initializable members are implicitly constrained as init-only auto-properties, not fields):

public data class Person { string Name = "Anonymous"; DateTime RegistrationTime = DateTime.Now; }

Now it's much easier to describe, even to novice users:

A data class / struct is like a normal class / struct except that all of its initializable members are public read-only properties and conveniently has a default constructor and operators defined on it (which you can override if you wish).

Initialization is also pretty straightforward and closely resembles the declaration pattern (note that unlike a normal class initializer, members with default values are optional and all others would be mandatory - since they are read/init-only)

var person = new Person { Name = "John Doe"; } // RegistrationTime defaults to DateTime.Now

Explicit constructors are still allowed, of course, as well as getter-only (non-auto-) properties, methods and indexers (not sure about events) and operate normally.

Final notes

Overall I admit I had a hard time understanding the intention of the design from the proposal text. It's possible I misunderstood some aspects, or that some unwritten alterations are in planning. On these cases please let me know and I'll try to correct it.

HaloFour commented 4 years ago

@rotemdan

https://github.com/dotnet/csharplang/issues/2699

I think there are two separate designs being worked here which would lead to orthogonal features.

One is a simpler POCO-like data carrier. That is the proposal that I linked which does more or less fit what you describe. The type is not positional and is based on existing auto-properties, but the data keyword introduces identity and other common concerns as well as exploring readonly types in initializers.

The latter is a positional data carrier akin to functional case classes that represent a context or state more than they represent an entity. They have the same concerns about identity but are intended for very different cases.

IMO there's room in the language for both. F# has both.

rotemdan commented 4 years ago

The syntax I suggested is very similar to the "alternative" Haskell record syntax, which I felt was a significantly better match to C#:

Haskell:

data Person = Person { firstName :: String, lastName :: String, age :: Int }

C#:

data class Person { string FirstName; string LastName; int Age; }

My intention was that initializable members (AKA fields) of a data class (which would otherwise appear like a regular class), would have an implicit order defined by the order of declaration (similarly to Haskell).

This ordering would then be used to define a default constructor, a Deconstruct method, a ToString method etc. Since all field-like members would be constrained to be public, this wouldn't violate any OO/encapsulation principles (exposing hidden data etc).

I see the connection with the concept of POCO objects (Plain Old CLR Objects), but it was not intentional. POCO object fields are usually not constrained to be read-only. Also I did not intend all fields to be easily serializable (though that would have been nice).

Essentially it goes in the direction of a generalization of the concept of a record I would describe as an explicit (pseudo-) stateless class. Explicit since all its defining data is public and pseudo-stateless because although its fields are read-only C# can't really guarantee them to be deeply immutable and functions pure. These classes/structs would be able to inherit from each other, have static members, have abstract/virtual members, implement interfaces (though not all of them) and future type classes etc. They would also allow for private/protected methods/getters/indexers.

On the issue of structs: I intended that unlike regular structs, a data struct would be much closer to data class (compared to a struct is to a class) as it would require construction before use, allow fields to have initializers, allow for inheritance etc. (this area still needs more investigation).

In effect trying to take advantage of the opportunity and attempt to "modernize" the class and struct system to a more stateless, thread-safe, easily serializable / cloneable system, while still maintaining familiar syntax and the high level of flexibility required by a general-purpose/multiparadigm programming language.

HaloFour commented 4 years ago

@rotemdan

This ordering would then be used to define a default constructor, a Deconstruct method

Note that adding a specific constructor and Deconstruct method would also mean that adding any members to a record would be a breaking change. So would reordering them. They'd also not be compatible with partial types due to the lack of implied positionality of the properties. That's why the proposal for nominal records is not using a constructor for creating an instance.

rotemdan commented 4 years ago

@HaloFour I'm aware of that - that's why I suggested trying to avoid the constructor syntax and instead try to concentrate on the object initializer syntax, that's less sensitive to positioning and adding/removal of members:

var person = new Person { Age = 25, LastName = "Doe", FirstName = "John" }

Where fields with no default initializers would be mandatory and the rest optional.

(In other words - I intended that a default constructor might be available, but wouldn't be the recommended method for construction)

I agree that the default Deconstruct method would be impacted by reordering or adding of members, but so would be its default implementation for records defined by a primary constructor (in relation to adding, removing or reordering of parameters). For the most part, for records that are more on the complex side, a full Deconstruct method wouldn't be that useful anyway and most likely would be customized by the user to omit unneccessary fields.

HaloFour commented 4 years ago

@rotemdan

The positionality of the default constructor is what informs the compiler of the order of the elements for the deconstructor, and without the developer explicitly defining that constructor the solution would be too brittle. Either records are positional or they are nominal, it doesn't make sense to try to make them both. Although if they are nominal, and manual addition of constructor and deconstructor would be fine.

I do think it makes sense to offer both types as plenty of smaller contextual types and members of DUs don't benefit from having to name those members.

rotemdan commented 4 years ago

@HaloFour

I just discovered the records v2 proposal, which isn't linked here and I wasn't aware of it before. The keyword I suggested (data class) was only coincidentally similar - it was not intentional.

Since this issue had a milestone and was recently marked as design notes I assumed it had the most up-to-date information.

I think my ideas about requiring fields to be public and readonly properties (at least by default) - which also cuts out some of the "clutter", the idea of implicit positionality, expansion of struct behavior, new constrained object system etc. might be useful for the design team. All I can do is hope they read this and try to make the best out of it. I understand the design process of the language is mostly closed, so at this time I don't find much value in deliberating further without feedback from the team itself.

kjata30 commented 4 years ago

A feature I would love to see in a future version of C# would be the typescript-like declaration and assignment of fields within a constructor signature. For example:

public class WeatherController
{
    public WeatherController(
        private readonly IRainFactory rainFactory, 
        private readonly ISunShineFactory sunShineFactory){}
}

rather than

public class WeatherController
{
    private readonly IRainFactory rainFactory;
    private readonly ISunShineFactory sunshineFactory;
    public WeatherController(IRainFactory rainFactory, ISunShineFactory sunShineFactory)
    {
        this.rainFactory = rainFactory;
        this.sunShineFactory = sunShineFactory;
    }
}

Is this something that could be rolled into this champion? Has this proposal been considered before? It seems like the sort of work required to complete the records feature would also potentially allow for this functionality... based on my understanding of the records proposal, the feature would allow for this sort of declaration and assignment but not allow for other members to be declared within the class.

wiktor-golonka commented 4 years ago

@kjata30: I'd love to use access modifiers in such way. Maybe even in more Kotlinish alike way:

public class WeatherController(
    IRainFactory rainFactory, 
    public ISunshineFactory sunshineFactory, 
    readonly ISnowFactory snowFactory)
{
}

that would translate to:


public class WeatherController
{
    private IRainFactory rainFactory;
    public ISunShineFactory sunshineFactory;
    private readonly WeatherController(
             IRainFactory rainFactory, 
             ISunShineFactory sunShineFactory, 
             ISnowFactory snowFactory)
    {
        this.rainFactory = rainFactory;
        this.sunShineFactory = sunShineFactory;
        this.snowFactory = snowFactory;
    }
}
Richiban commented 4 years ago

A feature I would love to see in a future version of C# would be the typescript-like declaration and assignment of fields within a constructor signature. For example:

public class WeatherController
{
    public WeatherController(
        private readonly IRainFactory rainFactory, 
        private readonly ISunShineFactory sunShineFactory){}
}

rather than

public class WeatherController
{
    private readonly IRainFactory rainFactory;
    private readonly ISunShineFactory sunshineFactory;
    public WeatherController(IRainFactory rainFactory, ISunShineFactory sunShineFactory)
    {
        this.rainFactory = rainFactory;
        this.sunShineFactory = sunShineFactory;
    }
}

Is this something that could be rolled into this champion? Has this proposal been considered before? It seems like the sort of work required to complete the records feature would also potentially allow for this functionality... based on my understanding of the records proposal, the feature would allow for this sort of declaration and assignment but not allow for other members to be declared within the class.

The feature you're after is primary constructors (one of my personal favourite hobby horses too), and it has gained a little traction at least.

Richiban commented 4 years ago

@kjata30 Pardon my assumption; I'm not sure I understand how you can mix field declarations into constructor declarations without also having a designated primary constructor.

For example, are you suggesting that you'd allow this:

public class C
{
    public C(private readonly string a){}
    public C(private readonly string b){}
    public C(private readonly string c){}
}

Does this mean that the class has all three fields, a, b, and c? Even though you can only call one constructor?

alrz commented 4 years ago

Regarding init-only members, isn't it the mechanism for nominal records (initialized by name)?

So instead of initonly we could make all public readonly stuff accessible in the object initializer.

public data class NominalRecord
{
  public int Property { get; }
  public readonly int Field;
}
new NominalRecord { Property = 1, Field = 0 };

I can imagine we could mix this with positional records as well.

That said, I'm not sure what initonly brings to the table beside just making everything more verbose.

orthoxerox commented 4 years ago

The latest notes: https://github.com/dotnet/csharplang/blob/47856e2351b4fa771cae1075c26cea7eae1887da/meetings/2020/LDM-2020-04-13.md

Liander commented 4 years ago

Enforcing named arguments in general is not something you can do today (AFAIK) which can be handled in the proposal by the using of the with-expression as invocation of .With(..) with named arguments.

Could that be extended to arbitrary methods with the meaning of saying from what position named arguments should be enforced on the invocation of any method?

The with-expression could then be used also on 'non-record' factory method like:

Message.Create(context) with
{
    Text = "Hello World!"
};  

with the meaning of Message.Create(context, text: "Hello World!");

So the 'with-expression' will turn into a general construction of enforcing named arguments from a given argument position given by the arguments applied before the expression and when it is applied on an identifier a '.With()' method is assumed.

(I haven’t read everything, and I apologize if this is something you have already discussed.)

Joe4evr commented 4 years ago

@Liander The language team is considering splitting object initialization into separate "phases", which would allow placing a plain object initializer after a call to a factory method, provided that the factory is annotated as such.

The upshot of this approach is that the name of the factory method can be anything and the developer can just hang an object initializer after the call.

// No 'with' necessary
var msg = Message.Create(context) { Text = "Hello World!" };

The application of 'with' then simply becomes a special case of the above:

var bob = tom with { FirstName = "Bob" };

Similar to how the LINQ query keywords will work on anything as long as there are proper methods exposed, the use of with could work if it can bind to a simple With() method that also has the factory method annotation, and no additional parameter list is necessary (meaning that there's no compat concerns as the type evolves).

Watch this video for more details.

CoolDadTx commented 4 years ago

Maybe this has been answered in one of the hidden discussions above by I don't see the data class syntax as being remotely useful. It reminds me of C++. A data class is not a record. Since you are clearly adding a new, context sensitive keyword to support records why not just call them records? The functionality of a record is so dramatically different than classes it doesn't make sense to even remotely tie them to class behavior. They more closely follow structs so even data struct would be slightly more logical to me. But given the dramatic differences I believe a brand new keyword completely makes sense even if they are implemented as classes under the hood.

YairHalberstadt commented 4 years ago

a data class is not a record. Since you are clearly adding a new, context sensitive keyword to support records why not just call them records?

This presumes that record is a meaningful concept, as is data class, and one is being implemented but it's being named the other. As far as I know though neither has a general well accepted meaning in this context.

CoolDadTx commented 4 years ago

Record is a well known term in other languages like VB and Pascal. It is also the name being used to describe this feature and with the addition of the with keyword VB folks are going to do the association. If record is not a meaningful concept then why is the feature being called Record and the blog article about the feature referring to it as record? Irrelevant of semantics folks are going to be referring to it as a record because of this.

To be correct everybody should be calling it data class which would then get people thinking about DTOs. Clearly there isn't a great name here but since the feature is being advertised as a "record" then it makes sense to use the same word in code. It would be like calling a class "class" but the language syntax referring to it as "non struct".

HaloFour commented 4 years ago

@CoolDadTx

"Records" are a fairly generic term in computing and apply to many nominal data structures. Many languages have their own flavor and they're often very different from one another. There really isn't a "correct" here.

CoolDadTx commented 4 years ago

Then stop calling them records in all the places it is currently being referenced... If the best term anybody has come up with to describe it is "a record" then that is what it is. The language should reflect what it is called.