Closed VBAndCs closed 3 years ago
The concept of a new record type that lies between Value Type and Reference Type is confusing, and practically has zero benefits. ... It's bad to make the record immutable.
That's a bold claim for you to make. Immutable objects do have valuable characteristics, particularly for opportunities in faster scalable multi-threaded (e.g. Async/Await) processing, and for more "algebraic"-style optimizations from the compiler (since side-effects are guaranteed not to happen by design). An immutable reference type presents an opportunity for reduced memory pressure. There are many computing scenarios which involve a forward-only read-only stream of input, and that sort of scenario seems a natural fit for this C# record
type. That much would explain why a struct
style of equality test was implemented for record
.
Food for thought: https://en.wikipedia.org/wiki/Immutable_object, https://blog.miguelbernard.com/c-9-0-records/, https://www.infoworld.com/article/3564161/how-to-use-immutability-in-csharp.html, https://www.thomasclaudiushuber.com/2020/09/01/c-9-0-records-work-with-immutable-data-classes/.
VB anonymous type with
key
fields is way smarter!
OTOH you need to "be smarter" in order to use them, because they are mutable; and if you don't want a given field to be mutable, you have to explicitly specify that behavior. C# anonymous types are less "smart"; but users also need to be less "smart", which is generally a good thing.
Every single language feature always has some sort of tradeoff; it's never black and white in one direction.
Your example of custom logic for evaluating equality is not very common; and it's probably better to optimize for the 90% or 99% case that two objects are considered equal if their fields are equal. Custom equality can be implemented using a custom class, or perhaps via a custom comparer. This is similar to how auto-property declarations are optimized for the simplest case: property which reads / writes directly from / to the backing field, and an optional initial value assignment to the backing field; everything beyond that simplest case requires a full property definition. This is better than trying to shoehorn every possible different property into the smallest possible syntax.
(And BTW it's LINQ or Linq or perhaps LInQ, never LinQ.)
@rskar-git: Try to separate between concepts and implementation. My issue here is the new Record object type, which is inconsistent with the CLR, which recognize only Structs and Classes. In C#, a Class can't inherit a record, but the record is a class so, it can be passes to a generic type constrained as a class! This is insane, and totally a very bad decision! The original proposal was to offer a compact syntax for defining data classes and structures, where you can use a readonly keyword to make it imutable:
ReadOnly Class Student(ID As Integer, Name As String)
The student class is a normal class, with some generated constructors, deconstructors and operators. This design was perfect, allow every one to use classes and structs to fit his needs. If the value equality is so important, the generated class can contain a method (say ValueEquals) that compares the two objects member-wisely. That's it. A general simple easy design. What they did is an abomination, that will blow up to their faces, with millions of issues and proposals that will complicate th4e language exponentially!
Note: For a custom equality, I suggest using lambda:
ReadOnly Class Student(
ID As Integer,
Name As String,
Class As Integer,
Function(std) {std.ID, std.Class}
)
Whrer the lambda sent to the last param defines the comparer. Which can be shorten using my dot lambda suggestion to:
ReadOnly Class Student(
ID As Integer,
Name As String,
Class As Integer,
{.ID, .Class}
)
Keep it simple. Keep it sane.
Do you really want to create a new object for each class in order to implement custom equality? Because every delegate is another object.
This is not a delegate. This is an expression tree like in ASP.NET Razor, that the compiler will use to generate the ValueEquals code. There will no calls within this method code.
Note that can have these records today if we want! We can write a code generator to implement these ideas even we can't use the suggested VB syntax directly. Something like this:
GenerateRecord(Name:="Student", Modifiers.ReadOnly, Type.Class,
{
("ID", GetType(Integer) ),
("Name" , GetType(String) ),
("Class", GetType(Integer) )
},
Function(std) {std.ID, std.Class}
)
Try to separate between concepts and implementation.
In what way, exactly, have I conflated those?
BTW, I believe you are wrong about the CLR, it isn't limited to recognize only Structs and Classes, otherwise F# would be a huger challenge to implement upon it.
C# distinguishes its record
from its class
(even though it is implemented via class
behind the scenes) for very important conceptual reasons - the point of which is to make sure its compiler can indulge in the sort of optimizations that F# employs.
This is not a delegate. This is an expression tree like in ASP.NET Razor, that the compiler will use to generate the ValueEquals code. There will no calls within this method code.
An expression tree is also an object; it may -- and usually does -- consist of multiple objects.
But even worse, this expression tree will need to be compiled, which is usually an order of magnitude worse than a simple method call, in terms of performance.
I am not familiar with how Razor parses expression trees, but I would imagine the expression tree is compiled into a function -- not an expression tree, and not a delegate -- whenever the Razor page is changed. Having syntax with similar behavior here would increase the potential ambiguity of the same syntax -- is it an expression tree? a delegate? or an equality definition which the compiler would convert to an actual method?
My translator translates C# records to VB Classes, I would love some feedback.
@paul1956 I gave it a try. You just converted it to a structure, with only the member fields. The record is a class with some auto-generated methods. You must generate those methods, to make the code using the record work in vb exactly as in C#.
@VBAndCs Fixed (will push soon) to create Class Block. Do you have an example of what's missing?
You can use reflection to see the generated C# class for the record.
I created a demo for an alternative design in VB.NET, that gave the same benefits with more flexibility, with no need at all to the init only properties:
=
works normally, but the Equality compares the key fields. All fields can readonly be keys (when we use the record keyword), or each individual property can be marked as a key or ReadOnly or both.
Not that the key and readonly keywords are not new in VB.NET. So, The only thing that I am introducing here is using the record or readonly keywords before the class or struct. This is a minimal non breaking change.?
after ref types in From method. I have no knowledge here of types since this is not a compiler.
Public record class Info(
X as Integer,
Y = "Ali",
Z as Date = Now
)
and this is the generated code:
Public Class Info
Public ReadOnly Property X As Integer
Public ReadOnly Property Y As Object
Public ReadOnly Property Z As Date
Public Sub New(x As Integer, Optional y As Object = "Ali", Optional z As Date = Now)
_X = X
_Y = Y
_Z = Z
End Sub
Public Shared Function From(anotherRecord As Info, Optional X As Integer? = Nothing, Optional Y As Object? = Nothing, Optional Z As Date? = Nothing) As Info
Return New Info( If(X Is Nothing, anotherRecord.X, X), If(Y Is Nothing, anotherRecord.Y, Y), If(Z Is Nothing, anotherRecord.Z, Z))
End Function
Public Overrides Function Equals(anotherObject) As Boolean
Dim anotherRecord = TryCast(anotherObject, Info)
If anotherRecord Is Nothing Then Return False
Return Equals(anotherRecord)
End Function
Public Overloads Function Equals(anotherRecord As {R.Name}) As Boolean
If Not X.Equals(anotherRecord.X) Then Return False
If Not Y.Equals(anotherRecord.Y) Then Return False
If Not Z.Equals(anotherRecord.Z) Then Return False
Return True
End Function
Public Shared Widening Operator CType(anotherRecord As Info) As (X As Integer, Y As Object)
Return (anotherRecord.X, anotherRecord.Y)
End Operator
Public Shared Widening Operator CType(fromTuple As (X As Integer, Y As Object)) As Info
Return new Info(fromTuple.X, fromTuple.Y)
End Operator
Public Shared Function From(fromTuple As (X As Integer, Y As Object)) As Info
Return new Info(fromTuple.X, fromTuple.Y)
End Function
Public Shared Widening Operator CType(anotherRecord As Info) As (X As Integer, Y As Object, Z As Date)
Return (anotherRecord.X, anotherRecord.Y, anotherRecord.Z)
End Operator
Public Shared Widening Operator CType(fromTuple As (X As Integer, Y As Object, Z As Date)) As Info
Return new Info(fromTuple.X, fromTuple.Y, fromTuple.Z)
End Operator
Public Shared Function From(fromTuple As (X As Integer, Y As Object, Z As Date)) As Info
Return new Info(fromTuple.X, fromTuple.Y, fromTuple.Z)
End Function
End Class
And these are some possible variations of syntax:
' immutable but with no keys, will not generate equals methods, so, it is a regular immutable class (or struct if you use Structure) :
Public Readonly class Info(
X as Integer,
Y = "Ali",
Z as Date = Now
)
' Using ReadOnly and Key with members:
Public class Info(
Key X as Integer,
ReadOnly Y = "Ali",
ReadOnly key Z as Date = Now
)
Note that I generate one constructor with required params for readonly properties that have no default values, and optional params for other properties. Note also that the From method can't set ref types to nothing unless they are nothing in the cloned record.
So, My question is: why C# complicated it so much, and invented too many new unnecessary concepts, to do so such a simple thing? I am afraid that C# took a wrong turn since C# 8.0, and can never recover from that! It is being more gibberish, more complicated, more incomparable with other .NET languages, and way too hard for beginners. Killing VB in such circumstances can make many developers desert .NET. If MS is happy of complicating C#, at least it should let VB.NET as an attractive door for beginners. But note that no one begins with a language without a future, and decays in the market and hiring in companies. On the other hand, if you are shutting down VB, MS must bring more of its spirit to C# to attract its developers and beginners. This is obviously not happening.
Note: In my demo, I generated a structure record (readonly or writable, with or without keys). This Value type record is the ultimatum solution for the ValueTuple issues like losing items names in reflection. So, this is can be useful when you need a reusable Tuple:
structure Student(Key ID As integer, Name As String)
Dim Std1 As Student = (1, "Ahmed")
Dim Std2 As new Student (2, "Ali")
Dim Std3 = Student.From((3, "Adam"))
I'm closing this out. We don't have issues about features not to have.
So... ¿The smart proposal of integrating record types to VB.NET was discarded at the end?.
Just for the
record
:) : I find C# records unuseful. The concept of a new record type that lies between Value Type and Reference Type is confusing, and practically has zero benefits. In practice, we don't always need to compare all fields of the record. VB anonymous type withkey
fields is way smarter! Besides, the=
is not the only comparison we need. We may want to use > and < to compare two students (or sort students) based on their AvgGrade property. So, I may suggest a new keyword (or just an attribute) to mark the AvgGrade property for this job. My recommendations here: 1- No need for the record keyword, and its complicated rules. It is just a class. 2- It's bad to make the record immutable. We must control that, by making the record fully mutable, fully immutable, or in between. 3- I always appreciate compact syntax. So, all formulas must apply to both structs and classes. 4- We may use LinQ in record syntax, to define whatever rules we want for properties. Ex:Class Student (Key FitrstName as string, Key SecondName as String, Order By AvgGrade as Integer)
In my opinion, C# is being poisoned in last two versions by pattern matching and records. I hope VB.NET avoid that.
Edit: I created RecGen to generate records in VB as I want them.