Git Product home page Git Product logo

Comments (277)

Richiban avatar Richiban commented on July 17, 2024 25

@orthoxerox I guess so, although the spec doesn't mention record parameters having accessibility modifiers, so I can't write:

public class ServiceA(private ServiceB serviceB)
{
    public void DoSomething()
    {
        // use serviceB here...
    }
}

And anyway, it would be a bit of an abuse of the records feature to accomplish this. My type isn't a record at all, and I don't want structural equality.

I remember when the C# team originally dropped primary constructors they said "We don't need this anymore because we're going to have records!" but I don't understand what this has to do with records... Sure, a record is also a type with a primary constructor, but any type should be able to have a primary constructor, not just records.

For reference, here are other languages which all support both primary constructors and records:

F#:

type Greeter(name : string) =
    member this.SayHello () = printfn "Hello, %s" name

Scala:

class Greeter(name: String) {
    def SayHi() = println("Hello, " + name)
}

Kotlin:

class Greeter(val name: String) {
    fun greet() {
        println("Hello, ${name}");
    }
}

Meanwhile, in C#, we're still writing this:

public class Greeter
{
    private readonly string _name;

    public Greeter(string name)
    {
        _name = name;
    }

    public void Greet()
    {
        Console.WriteLine($"Hello, {_name}");
    }
}

from csharplang.

Richiban avatar Richiban commented on July 17, 2024 23

While I'm very much looking forward to the introduction of Records into the language, I really don't like the chosen syntax: public class Point(int x, int y), primarily because it precludes the possibility of ever re-adding primary constructors into the language:

public class ServiceA(ServiceB serviceB)
{
    public void DoSomething()
    {
        // use field serviceB here...
    }
}

God I miss those... I am so sick of writing dumb constructors! :-P

Isn't

public record Point
{
    int X;
    int Y;
}

A better syntax? It leaves primary constructors open but is still about as syntactically short as is possible.

from csharplang.

gulshan avatar gulshan commented on July 17, 2024 15

Copying my comment on Record from roslyn-

Since almost a year has passed, I want to voice my support for the point mentioned by @MgSam -

I still see auto-generation of Equals, HashCode, is, with as being a completely separable feature from records.
I think this auto-generation functionality should be enabled its own keyword or attribute.

I think the primary constructor should just generate a POCO. class Point(int X, int Y) should just be syntactical sugar for-

class Point
{
    public int X{ get; set; }
    public int Y{ get; set; }
    public Point(int X, int Y)
    {
        this.X = X;
        this.Y = Y;
    }
}

And a separate keyword like data or attribute like [Record] should implement the current immutable, sealed class with auto-generated hashcode and equality functions. The generators may come into play here. Kotlin uses this approach and I found it very helpful. Don't know whether this post even counts, as language design has been moved to another repo.

from csharplang.

gafter avatar gafter commented on July 17, 2024 14

@gulshan Tuples are good for places where you might have used a record, but the use is not part of an API and is nothing more than aggregation of a few values. But beyond that there are significant differences between records and tuples.

Record member names are preserved at runtime; tuple member names are not. Records are nominally typed, and tuples are structurally typed. Tuples cannot have additional members added (methods, properties, operators, etc), and its elements are mutable fields, while record elements are properties (readonly by default). Records can be value types or reference types.

from csharplang.

louthy avatar louthy commented on July 17, 2024 9

For anybody who really wants record like functionality but doesn't want to wait, then I have built a pretty slick solution in language-ext:

Record<A>

Simply derive your type from Record<A>, where A is the type you're defining:

    public sealed class TestClass : Record<TestClass>
    {
        public readonly int W;
        public readonly string X;
        public readonly Guid Y;
        public AnotherType Z { get; set; }

        public TestClass(int w, string x, Guid y, AnotherType z)
        {
            W = w;
            X = x;
            Y = y;
            Z = z;
        }
    }

This gives you Equals, IEquatable.Equals, IComparable.CompareTo, GetHashCode, operator==, operator!=, operator >, operator >=, operator <, and operator <= implemented by default. As well as a default ToString implementation and an ISerializable implementation. Equality operations are symmetric.

Note that only fields or field backed properties are used in the structural comparisons and hash-code building. So if you want to use properties then they must not have any code in their getters or setters.

No reflection is used to achieve this result, the Record type builds the IL directly, and so it's as efficient as writing the code by hand.

There are some unit tests to see this in action.

Opting out

It's possible to opt various fields and properties out of the default behaviours using the following attributes:

  • Equals() - OptOutOfEq
  • CompareTo() - OptOutOfOrd
  • GetHashCode() - OptOutOfHashCode
  • ToString() - OptOutOfToString
  • Serialization - OptOutOfSerialization (can also use NonSerializable)

For example, here's a record type that opts out of various default behaviours:

    public class TestClass2 : Record<TestClass2>
    {
        [OptOutOfEq]
        public readonly int X;

        [OptOutOfHashCode]
        public readonly string Y;

        [OptOutOfToString]
        public readonly Guid Z;

        public TestClass2(int x, string y, Guid z)
        {
            X = x;
            Y = y;
            Z = z;
        }
    }

And some unit tests showing the result:

public void OptOutOfEqTest()
{
    var x = new TestClass2(1, "Hello", Guid.Empty);
    var y = new TestClass2(1, "Hello", Guid.Empty);
    var z = new TestClass2(2, "Hello", Guid.Empty);

    Assert.True(x == y);
    Assert.True(x == z);
}

RecordType<A>

You can also use the 'toolkit' that Record<A> uses to build this functionality in your own bespoke types (perhaps if you want to use this for struct comparisons or if you can't derive directly from Record<A>, or maybe you just want some of the functionality for ad-hoc behaviour):

The toolkit is composed of seven functions:

    RecordType<A>.Hash(record);

This will provide the hash-code for the record of type A provided. It can be used for your default GetHashCode() implementation.

    RecordType<A>.Equality(record, obj);

This provides structural equality with the record of type A and the record of type object. The types must match for the equality to pass. It can be used for your default Equals(object) implementation.

    RecordType<A>.EqualityTyped(record1, record2);

This provides structural equality with the record of type A and another record of type A. It can be used for your default Equals(a, b) method for the IEquatable<A> implementation.

    RecordType<A>.Compare(record1, record2);

This provides a structural ordering comparison with the record of type A and another record the record of type A. It can be used for your default CompareTo(a, b) method for the IComparable<A> implementation.

    RecordType<A>.ToString(record);

A default ToString provider.

    RecordType<A>.SetObjectData(record, serializationInfo);

Populates the fields of the record from the SerializationInfo structure provided.

    RecordType<A>.GetObjectData(record, serializationInfo);

Populates the SerializationInfo structure from the fields of the record.

Below is the toolkit in use, it's used to build a struct type that has structural equality, ordering, and hash-code implementation.

    public class TestStruct : IEquatable<TestStruct>, IComparable<TestStruct>, ISerializable
    {
        public readonly int X;
        public readonly string Y;
        public readonly Guid Z;

        public TestStruct(int x, string y, Guid z)
        {
            X = x;
            Y = y;
            Z = z;
        }

        TestStruct(SerializationInfo info, StreamingContext context) =>
            RecordType<TestStruct>.SetObjectData(this, info);

        public void GetObjectData(SerializationInfo info, StreamingContext context) =>
            RecordType<TestStruct>.GetObjectData(this, info);

        public override int GetHashCode() =>
            RecordType<TestStruct>.Hash(this);

        public override bool Equals(object obj) =>
            RecordType<TestStruct>.Equality(this, obj);

        public int CompareTo(TestStruct other) =>
            RecordType<TestStruct>.Compare(this, other);

        public bool Equals(TestStruct other) =>
            RecordType<TestStruct>.EqualityTyped(this, other);
    }

I realise this isn't strictly C# language talk; but I figured this was such a big pain point for most C# devs that you all wouldn't mind :)

from csharplang.

gafter avatar gafter commented on July 17, 2024 8

We are hoping to have records defined without a separate keyword. Parameters of the primary constructor become public readonly properties of the class by default. See https://github.com/dotnet/csharplang/blob/master/proposals/records.md#record-struct-example for an example.

from csharplang.

hickford avatar hickford commented on July 17, 2024 8

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#

from csharplang.

orthoxerox avatar orthoxerox commented on July 17, 2024 7

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.

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on July 17, 2024 7

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.

from csharplang.

gulshan avatar gulshan commented on July 17, 2024 6

Wouldn't it be better if primary constructors were not exclusive to records? Now, according to this proposal, primary constructors cannot be considered without adding the baggage of extra "record" tidbits. Refactoring a current class to use primary constructors(and thus record) is not a good choice then, as the behavior may change.

from csharplang.

Richiban avatar Richiban commented on July 17, 2024 6

@AustinBryan

You deliberately took the longest syntax to write that your example.

I think you've misunderstood my complaint. Yes, of course, you can make the code physically shorter by using expression-bodied members, but that's not the point. The point is that I'm forced to write declarations and statements that I shouldn't, because it's such a common scenario that there should be a shorthand for it. Even in your shorter example where we have a class with a single dependency called "name" of type string, you've still had to write the word "name" four times. Every single dependency requires 1. a field declaration 2. a constructor argument declaration and 3. an assignment from one to the other. This is frustrating and, honestly, quite indefensible. Using your shorter version I want to be able to write:

class Greeter(string name) {
    public void Greet() => Console.WriteLine($"Hello, {name}");
}

Using expression-bodied constructors also falls down as soon as there's more than one field (which would be 80% of the time), unless you're advocating for:

class MyService
{
    private readonly IServiceA _serviceA;
    private readonly IServiceB _serviceB;
    private readonly IServiceC _serviceC;

    public MyService(IServiceA serviceA, IServiceB serviceB, IServiceC serviceC) =>
      (_serviceA, _serviceB, _serviceC) = (serviceA, serviceB, serviceC);

    // Actual methods go here...
}

Not easy to read, which means it's a breeding ground for bugs.

In these examples the constructor is pure boiler plate; it serves no purpose for the actual behaviour of the class and merely introduces noise that distracts from the class's functionality.

Note: I'm (of course) not advocating that we remove the tried and true constructor syntax we already have from the language. If you want to do argument validation or run some code in the constructor then the full syntax will always be available to you.

I'm merely arguing that we need a syntax to support this very common scenario where a constructor exists for the sole purpose of setting fields. A way of declaring "This is my class and these are its dependencies", if you will.

from csharplang.

theunrepentantgeek avatar theunrepentantgeek commented on July 17, 2024 6

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.

from csharplang.

gulshan avatar gulshan commented on July 17, 2024 5

As records are considering positional pattern matching, which is actually a tuple feature, and tuples can have named members, which is actually a record feature, I think there is some overlapping between the two. How about making making seamless translations between struct records and tuples based on position, if types match? Names of the members will be ignored in these translations. Struct records will then just become named tuples I guess. Implementations are already similar.

from csharplang.

Richiban avatar Richiban commented on July 17, 2024 5

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.

from csharplang.

miniBill avatar miniBill commented on July 17, 2024 4

I see a little problem with the proposed GetHashCode implementation in the proposal: if one of the properties is null, the object hash code will always be zero. Wouldn't it be better to simply ?? 0 individual hash codes before multiplying and summing?

from csharplang.

gafter avatar gafter commented on July 17, 2024 4

@Richiban No, if a property is explicitly written into the body by the programmer, as in my example, then the compiler does not produce one. That is described in the specification.

from csharplang.

jhickson avatar jhickson commented on July 17, 2024 4

We would find record types very useful in implementing strong types (for want of a better name) to represent specific domain data types such as identifiers, names, etc. For instance, we would use a Port type that has an int data member rather than a bare int to represent a port for a socket. So we have a lot of types (usually structs) that:

  • Have a single, private, unexposed data member
  • Implement ToString(), equality (including IEquatable<>) and sometimes comparability in terms of that data member
  • Implement explicit cast operators to and from the data member's type

Code snippets help the generation of new types of this kind but language support would obviously be of great benefit and records seem to mostly fit that bill. We don't see these strong types as records but the record syntax proposed here would help save a lot of boilerplate code particularly if they implement IEquatable<> automatically.

That said, as we tend to not directly expose the wrapped value - it's present as a private data member only. So we would find very useful if not only were we able to control the access modifier of the generated property. I realise it has been proposed to allow this by explicitly implementing the property in question, but it would be good to have a more straightforward means of doing that. So, rather than writing

public struct Port(int Value)
{
    private int Value { get; }
}

it would be good to be able to write something like the following instead

public struct Port(private int Value);

Is that a viable proposal?

It would also be good to be able to easily specify that if you have only one data member then explicit cast operators should be auto-generated as well, but I'm afraid I'm a loss as to how to express that cleanly, and can see that special-casing like this wouldn't make for a nice language feature.

from csharplang.

darcythomas avatar darcythomas commented on July 17, 2024 4

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

from csharplang.

rotemdan avatar rotemdan commented on July 17, 2024 4

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.

from csharplang.

orthoxerox avatar orthoxerox commented on July 17, 2024 4

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

from csharplang.

Joe4evr avatar Joe4evr commented on July 17, 2024 3

Just had a thought about the Primary Constructor part of this: Since a dev is able to specify multiple constructors anyway (especially given the example of versioning a type, where the previous PC gets "demoted" to a hand-written one so back-compat remains maintained), is it too much effort to have the compiler put a generated marker attribute on the generated Primary Constructor?

The idea here is that an API consumer would get that specific constructor suggested first by IntelliSense after new RecordType(, rather than the traditionally top one (which I think is by number of parameters in the first pass).

from csharplang.

HaloFour avatar HaloFour commented on July 17, 2024 3

@rotemdan

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

from csharplang.

CoolDadTx avatar CoolDadTx commented on July 17, 2024 3

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.

from csharplang.

gafter avatar gafter commented on July 17, 2024 2

@Richiban
In the primary constructor feature, you could have only used the primary constructor parameter in field and property initializers; parameters are not captured to a field automatically. You could get what you want using records in essentially the same way you would have done using primary constructors:

public class Greeter(string Name)
{
    private string Name { get; } = Name;
    public void Greet()
    {
        Console.WriteLine($"Hello, {Name}");
    }
}

from csharplang.

gafter avatar gafter commented on July 17, 2024 2

@Richiban We no longer restrict record types to be sealed or abstract. The following design note in the spec describes how equality is likely to work:

Design notes: Because one record type can inherit from another, and this implementation of Equals would not be symmetric in that case, it is not correct. We propose to implement equality this way:

    public bool Equals(Pair other) // for IEquatable<Pair>
    {
        return other != null && EqualityContract == other.EqualityContract &&
            Equals(First, other.First) && Equals(Second, other.Second);
    }
    protected virtual Type EqualityContract => typeof(Pair);

Derived records would override EqualityContract. The less attractive alternative is to restrict inheritance.

from csharplang.

lachbaer avatar lachbaer commented on July 17, 2024 2

I can't imagine what methods would be in IWithable

The With(...) methods. Obviously the concrete signature for the methods of that interface is missing. Well, it's a cause for thought. 😇

--
Synapse explosion 😝 - because the with keyword will probably be introduced with the emerge of record types, this keyword could be seen as a pseudo interface.

class Point(int X, int Y) : auto with, auto IEquatable<Point> {}

Without it no With(...) methods for this class are produced

With auto interfaces you leave it to the user how much functionality is synthesized and keep it open for future extensions, when some come to your mind 😊

from csharplang.

AustinBryan avatar AustinBryan commented on July 17, 2024 2

@Richiban You deliberately took the longest syntax to write that your example.

F#:

type Greeter(name : string) =
    member this.SayHello () = printfn "Hello, %s" name

Meanwhile, in C#, we're still writing this:

public class Greeter
{
    private readonly string _name;

    public Greeter(string name)
    {
        _name = name;
    }

    public void Greet()
    {
        Console.WriteLine($"Hello, {_name}");
    }
}

If that bothers you, you can write this:

class Greeter {
    readonly string _name;
    public Greeter(string name) => _name = name;
    public void Greet() => Console.WriteLine($"Hello, {_name}");
}

That went from 14 lines to only 5, which is only one more than Kotlin, two more than Scala and three more than F#.

we're still

I don't know about most C# programmers, but I for one use expression bodies whenever I can.

from csharplang.

 avatar commented on July 17, 2024 2

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.

from csharplang.

Richiban avatar Richiban commented on July 17, 2024 2

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

from csharplang.

popcatalin81 avatar popcatalin81 commented on July 17, 2024 2

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.

from csharplang.

HaloFour avatar HaloFour commented on July 17, 2024 2

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

from csharplang.

lachbaer avatar lachbaer commented on July 17, 2024 1

Why do you need the with keyword in

p = p with { X = 5 };

Wouldn't it be equally understandable when there were a .{-Operator? It would allow for more brief chaining

var r = p.{ X = 5, Y = 6 }.ToRadialCoordinates();

from csharplang.

orthoxerox avatar orthoxerox commented on July 17, 2024 1

@Richiban The comparison goes something like this:

public override Type TypeTag => typeof(Foo);

public bool Equals(Foo other)
{
    if (typeof(Foo) != other.TypeTag) return false;
   ...
}

Of course, if anyone inherits from Foo and doesn't override TypeTag, they have only themselves to blame. Maybe the devs will switch to GetType(), which is slower, but works automatically.

from csharplang.

jnm2 avatar jnm2 commented on July 17, 2024 1

@fubar-coder I'm curious, does that ever make a difference besides being annoying if you ever refactor to being non-abstract?

from csharplang.

gafter avatar gafter commented on July 17, 2024 1

We could make a special rule saying that in an abstract class the compiler-generated constructor is protected instead of public. But there would be literally no point in making that rule or implementing it in the compiler.

from csharplang.

gafter avatar gafter commented on July 17, 2024 1

I can't imagine what methods would be in IWithable. If it is empty, then : auto IWithable is an awfully strange syntax to opt in to the compiler generating something that has nothing to do with this interface.

from csharplang.

gafter avatar gafter commented on July 17, 2024 1

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.

from csharplang.

notanaverageman avatar notanaverageman commented on July 17, 2024 1

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

A few advantages I can think of:

  • It delegates the responsibility to tooling instead of adding complexity to the language.
  • Anyone can write their own generators if they are not satisfied with the mainstream one. (Maybe you never write one.)
  • Can be easily extended with more attributes. (Do not want equality generation? Use [DoNotGenerateEquals]. Want to exclude a property from equality? Use [ExcludeFromEquals], ...)
  • Users can see the generated code.

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.

from csharplang.

alrz avatar alrz commented on July 17, 2024 1

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.

from csharplang.

Liander avatar Liander commented on July 17, 2024 1

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

from csharplang.

Joe4evr avatar Joe4evr commented on July 17, 2024 1

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

from csharplang.

orthoxerox avatar orthoxerox commented on July 17, 2024

See #77 regarding with expressions that are more useful than bare With methods.

from csharplang.

gulshan avatar gulshan commented on July 17, 2024

From this recent video by Bertrand Le Roy, it seems records are being defined with a separate keyword and the primary constructor is back with shorter syntax. So far I have understood, the new primary constructor means parameters of primary constructor are also fields of the class-

class Point(int x, int y)
// is same as
class Point
{
    int x { get; }
    int y { get; }

    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

It seems the field access modifier is defult/private and to expose them separate public properties are needed like this-

class Point(int x, int y)
{
    public int X => x;
    public int Y => y;
}

I like the idea and I think there should be more discussions about these ideas here.

from csharplang.

gulshan avatar gulshan commented on July 17, 2024

Object(and collection, index) initializers getting constructor level privilege for initializing fields/properties can enable some interesting scenarios of complex hierarchical object initialization.

from csharplang.

gafter avatar gafter commented on July 17, 2024

@gulshan Can you please back up that assertion with an example? I don't see how using an object initializer instead of a constructor enables anything.

from csharplang.

jnm2 avatar jnm2 commented on July 17, 2024

@miniBill Yes.

from csharplang.

orthoxerox avatar orthoxerox commented on July 17, 2024

@Richiban What about https://github.com/dotnet/csharplang/blob/master/proposals/records.md#primary-constructor-body ? That looks like a primary constructor to me.

from csharplang.

Richiban avatar Richiban commented on July 17, 2024

@gafter If we go with the record definition public class Greeter(string Name) doesn't Name get lifted into a public property? That's the main reason I wouldn't want to use it for something that's not strictly a record--I don't necessarily want a type to expose its dependencies. Can I give accessibility modifiers to record fields?

from csharplang.

Richiban avatar Richiban commented on July 17, 2024

By the way, do I understand the spec right that class records must be either sealed or abstract?

There are serious problems with allowing classes to derive types that have defined custom equality: https://richiban.uk/2016/10/26/why-records-must-be-sealed/

from csharplang.

MgSam avatar MgSam commented on July 17, 2024

If records remain the only way to get auto-generation of Equals and HashCode then I think they absolutely should not be sealed. As you yourself state in your post, doing a simple type check in the equals method solves the issue you bring up. Seems pretty Byzantine to wall off an entire use case because of the fact that developers "might" misuse a feature.

Getting structural equality right in C# is already a minefield that most sensible developers let IDE tools generate code for. Compiler autogeneration of the equality methods should be enabled for the widest net of situations possible.

from csharplang.

orthoxerox avatar orthoxerox commented on July 17, 2024

@Richiban last time I asked, the LDT planned to relax this restriction and compare runtime types in Equals.

from csharplang.

Richiban avatar Richiban commented on July 17, 2024

@orthoxerox @MgSam Yes, a runtime check is correct if you assert that the two objects have exactly the same type, not just that they have some common base type, i.e.

a.GetType() == typeof(Person) && b.GetType() == typeof(Person)

rather than

a is Person && b is Person

Also, I would like to clarify my position in that I'm not trying to prevent "developers misusing a feature" but rather pointing out that the language / runtime will not only allow a blatantly incorrect comparison between two objects of different types but could potentially return true at runtime.

from csharplang.

fubar-coder avatar fubar-coder commented on July 17, 2024

The constructor in the abstract record class example should be protected, not public.

from csharplang.

fubar-coder avatar fubar-coder commented on July 17, 2024
  1. The visibility changing from protected to public should happen automatically as long as you don't provide the primary constructor
  2. public on an abstract class doesn't make sense, because you cannot instantiate this class using this publicly visible constructor

from csharplang.

jnm2 avatar jnm2 commented on July 17, 2024

public on an abstract class doesn't make sense, because you cannot instantiate this class using this publicly visible constructor

Sure it does, as much sense as public members on an internal class. public never overrides other visibility restrictions. It just indicates that there are no additional restrictions being imposed. public on a abstract class's constructor means "there's nothing special about this member, it just follows the visibility rules of the containing class."
The fact that the class is abstract imposes a visibility restriction on the constructor already so in that sense it's redundant to specify protected unless you're trying to encode an extra bit of information that the constructor would still be protected even if the class were not abstract.

from csharplang.

GeirGrusom avatar GeirGrusom commented on July 17, 2024

A public constructor for an abstract class is for all intents and purposes protected. It can't be called except by a derived class. Public members are not necessarily pointless on internal classes; they can implement interfaces. Constructors, however, are never part of an interface declaration and will never be publicly accessible.

In my opinion, if only for reflection, the abstract class constructor should be protected. There is literally no point in making them public.

from csharplang.

lachbaer avatar lachbaer commented on July 17, 2024

I just wrote over in dotnet/roslyn#10154 (comment) but it seems to be more appropriate here. So I'm gonna copy my comment.

My 5 cents: parts of the record type could be automatically implemented by interfaces, if requested by the user with the auto keyword. Otherwise it is a POCO, with only the auto-properties and constructor.

class Point(int X, int Y) : auto IWithable, auto IEquatable<Point> {}

The standard object method overrides should always be created automatically, as should the operator is.

The advantage is that you can incrementally add functionality to the record types when the compiler and framework evolves. The record types are downward compatible, because the user can cast them to the necessary interface.

To ease with the available interfaces, there can be a summarizing interface, like 'IRecordTypeBase<>' that implements basic functionality as suggested currently, and 'IRecordTypeNet50<>' for additional functionality provided by .Net 5.0 and deriving 'IRecordTypeBase<>'.

from csharplang.

lachbaer avatar lachbaer commented on July 17, 2024

Or to create a POCO, opt-out:

explicit class Point(int X, int Y) {}
class Point(int X, int Y) : explicit {}

(object overrides and c'tor should of course always be created)

from csharplang.

Richiban avatar Richiban commented on July 17, 2024

@jhickson

public struct Port(private int Value);

I'd love this, but I'm afraid I've asked for it before and I think the decision has been made.

As for casting, I think that people's expectations are a little too divergent to auto-generate this behaviour. Some won't like casting, some will be happy to have both casts, some will want a cast that only goes one way, and some will want a cast that goes implicitly one way but explicitly the other.

from csharplang.

jhickson avatar jhickson commented on July 17, 2024

@Richiban
Somehow I missed you proposing that further up. I'm not sure how.

I can see proposing auto-generation of casting could open a can of worms, and I can also see it probably wouldn't be desirable anyway given it would only be applicable to a subset of record types (i.e. only those with one member).

from csharplang.

Richiban avatar Richiban commented on July 17, 2024

@jhickson

Somehow I missed you proposing that further up. I'm not sure how.

Don't worry, it's not on this thread!

from csharplang.

gafter avatar gafter commented on July 17, 2024

@jhickson I'd rather get records of any kind sooner and then add this extension later. Adding it later would be completely upward compatible.

from csharplang.

jhickson avatar jhickson commented on July 17, 2024

@gafter I completely understand that. I'll have to make sure I'm earlier to the party when any extensions are proposed. Thanks.

from csharplang.

gordanr avatar gordanr commented on July 17, 2024

@jhickson Record types will really be useful for implementing strong domain types. I am particularly interested in some other extension. How to ensure that created domain object always satisfy some invariants (created object is always in valid state).

from csharplang.

jhickson avatar jhickson commented on July 17, 2024

@gordanr I agree: a big advantage of strong types is being able to specify constraints. I believe the proposal allows for the explicit definition of the primary constructor body though, and possibly that would be sufficient.

from csharplang.

darcythomas avatar darcythomas commented on July 17, 2024

I like to write classes which are either just functional services (no state) or just hold data (all state, no functionality). It would be nice to be able to have my generic functions only accept types of T where T is a Record type. So if Record types either inherit from a Record abstract class or implement an interface (IWithable?) that would help with that.

from csharplang.

alrz avatar alrz commented on July 17, 2024

After pattern-matching this is not much of a surprise.

http://cr.openjdk.java.net/~briangoetz/amber/datum.html (check parent dir for more)

from csharplang.

orthoxerox avatar orthoxerox commented on July 17, 2024

@alrz to paraphrase Picasso, "good language designers borrow..."

from csharplang.

GeirGrusom avatar GeirGrusom commented on July 17, 2024

Would it make sense to implement == and != for record structs?

from csharplang.

bugproof avatar bugproof commented on July 17, 2024

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

from csharplang.

gafter avatar gafter commented on July 17, 2024

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.

from csharplang.

carlreinke avatar carlreinke commented on July 17, 2024

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.

from csharplang.

HaloFour avatar HaloFour commented on July 17, 2024

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

from csharplang.

CyrusNajmabadi avatar CyrusNajmabadi commented on July 17, 2024

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.

from csharplang.

 avatar commented on July 17, 2024

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

from csharplang.

popcatalin81 avatar popcatalin81 commented on July 17, 2024

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?

from csharplang.

popcatalin81 avatar popcatalin81 commented on July 17, 2024

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

from csharplang.

wanton7 avatar wanton7 commented on July 17, 2024

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

from csharplang.

popcatalin81 avatar popcatalin81 commented on July 17, 2024

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

from csharplang.

wanton7 avatar wanton7 commented on July 17, 2024

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

from csharplang.

popcatalin81 avatar popcatalin81 commented on July 17, 2024

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

from csharplang.

Richiban avatar Richiban commented on July 17, 2024

@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() => ...
}

from csharplang.

popcatalin81 avatar popcatalin81 commented on July 17, 2024

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

from csharplang.

Richiban avatar Richiban commented on July 17, 2024

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

from csharplang.

MarkusAmshove avatar MarkusAmshove commented on July 17, 2024

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?

from csharplang.

andre-ss6 avatar andre-ss6 commented on July 17, 2024

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

from csharplang.

gafter avatar gafter commented on July 17, 2024

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

from csharplang.

andre-ss6 avatar andre-ss6 commented on July 17, 2024

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.

from csharplang.

rotemdan avatar rotemdan commented on July 17, 2024

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.

from csharplang.

HaloFour avatar HaloFour commented on July 17, 2024

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

from csharplang.

rotemdan avatar rotemdan commented on July 17, 2024

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

from csharplang.

HaloFour avatar HaloFour commented on July 17, 2024

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

from csharplang.

rotemdan avatar rotemdan commented on July 17, 2024

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

from csharplang.

kjata30 avatar kjata30 commented on July 17, 2024

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.

from csharplang.

wiktor-golonka avatar wiktor-golonka commented on July 17, 2024

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

from csharplang.

Richiban avatar Richiban commented on July 17, 2024

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.

from csharplang.

Richiban avatar Richiban commented on July 17, 2024

from csharplang.

CoolDadTx avatar CoolDadTx commented on July 17, 2024

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.

from csharplang.

YairHalberstadt avatar YairHalberstadt commented on July 17, 2024

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.

from csharplang.

CoolDadTx avatar CoolDadTx commented on July 17, 2024

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

from csharplang.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.