'make invalid state unrepresentable' - that is an excellent line
@WillEhrendreich11 ай бұрын
Very idiomatic in fsharp, it's just a very natural way to get things done on that side of dotnet. It's an absolute joy to work with that philosophy.
@user-tk2jy8xr8b7 ай бұрын
Unfortunately you still can't have a truly non-nullable reference types nor guarantee struct initialization thanks to every type having `default`
@lorenzodepasquale11 ай бұрын
That NonEmptyString is so elegant
@curtmantle748611 ай бұрын
Devs should watch this even if they think they understand records.
@Cool-Game-Dev11 ай бұрын
I did
@abj13611 ай бұрын
To be sure, I 100% understood what you were describing about records in the opening of this video. But I knew approximately 0% of the syntax you showed after, so it’s a good thing I did not skip the rest of this video per your advice.
@williamturns164711 ай бұрын
This was a succinct deep-dive into records. Could you kindly make one on pattern matching, too? There are countless ways to use them and it doesn't come naturally to think in pattern-matching style after years of using verbose conditions.
@matheosmattsson281111 ай бұрын
Love it. Used records a few times but always confused me what they really are tbh. Other than a quick way to create a small class (which is what I saw it as earlier). Now I'm kind of considering migrating a lot of classes to records.. Great video!
@zoran-horvat11 ай бұрын
You have made a valid point. In a domain model of a significant complexity, many types are actually values in their heart. Once you start recognizing those models, you will find many examples where records fit naturally.
@TayyabMughalDIK11 ай бұрын
This is the best demo on records. It couldn't get better than this one.
@zoran-horvat11 ай бұрын
Thanks!
@nickbarton319111 ай бұрын
Didn't even think about deriving records, didn't know about equality. Glad to dump structs. Always learn something from these videos.
@guilhermealves5776 ай бұрын
One of the bests vídeos i watched in KZbin. Pure gold.
@averyrealtree54510 ай бұрын
What a wonderful guide! I love your concise and clear communication
@mr_venik11 ай бұрын
NonEmptyString record struct is nice, but in C# you can still use "default" keyword which would ignore parameterless constructor
@zoran-horvat11 ай бұрын
That is an interesting point about default. Did you check it? I can't find in the documentation whether it invokes the constructor or not.
@protox411 ай бұрын
@@zoran-horvat It does not invoke the parameterless constructor. It simply zeroes the struct. It would be hugely breaking otherwise (default is used to clear values to be GC'd everywhere).
@zoran-horvat11 ай бұрын
@@protox4 Yes, you're right. Thank you for this note, I will make sure to remember it the next time I speak about the same matter.
@user-tk2jy8xr8b7 ай бұрын
@@protox4 right, it's a straight memzero call. Likely that's the way lang designers answered the question "how do we initialize an array of a non-zero length"
@Mefator11 ай бұрын
You can also simply use *readonly record struct* to make its properties immutable by default.
@zoran-horvat11 ай бұрын
You are right, I should have shown that, too.
@Lonchanick11 ай бұрын
Enough material for at least 10 videos :) Nice!
11 ай бұрын
Good stuff. I was looking for the video to discuss with dotnet team in the format: let's meet after a week and discuss it. Your topics so far were in the shelve labeled "this could be too difficult for junios and mids", but this one is flexible. Juniors and seniors will find topics to discuss. Thank you.
@ДинаЛинская8 ай бұрын
Excellent, full and deep explanation, thanks a lot 🎉
@zo1dberg7 ай бұрын
Great vid and love your accent and storytelling!
@weluvmusicz11 ай бұрын
In the record you can define required immutable properties like so: public required string FirstName { get; init; }
@milosmrdovic723325 күн бұрын
Really useful video! Thanks, Zoran. However, there's one significant problem with your NonEmptyString implementation. The way you implemented the validation on it does not prevent someone from making a non-zero length array of NonEmptyStrings, using the "with { Value = null }" expression, or even worse, simply forgetting to initialize a NonEmptyString field. In all these cases your parameterless constructor that throws will not be called. Say, for example, you have a Person class with NonEmptyString Name property and you forget to initialize it - the Value of the Person's Name will end up being null. This behavior defeats the purpose of having the NonEmptyString type in the first place. If you want to enforce validation of your members so they don't end up with default values, you must use the standard reference-type record. Even then, you must be careful with auto-implemented init properties which can be set to whatever using the "with" expression.
@gard20545 ай бұрын
The best 13 min ever. Amazing explanation
@RezaPouyaSenDev5 ай бұрын
It was very good explanations. Thank you
@osman340414 күн бұрын
Would you also use record struct to model money types in retail domains like ListPrice, CostPrice, Total, GrossProfit etc….?
@mahmmoudkinawy27838 ай бұрын
thank you! excellent explanations
@ДмитрийКондратенко-б5ь11 ай бұрын
I wondering if records can be used as entity models working with EF
@zoran-horvat11 ай бұрын
Not by default, because records are immutable by default and EF tracks entities by reference. Even the terminology uncovers the issue - EF is dealing with entities and records define values.
@franciscohenriquez612112 күн бұрын
Interesting video Sir, Is there any way to use DataAnnotations using record types as DTO ? if so, would that affect the immutability of that object ?
@DimitrisConstantinou11 ай бұрын
I wish i had your deep knowledge of c#
@HuneSilver11 ай бұрын
thanks for the subtitles in english are very useful 🙂
@kinglygaurav11 ай бұрын
Besides learning records, if anyone wants to learn effective communication, watch this!
@kennyhendricks429311 ай бұрын
Damn!!! I love this, I generally use it in DTOs scenario
@adollphus6 ай бұрын
Great content, thanks Zoran.
@barionlp11 ай бұрын
never heard that i should use record structs over normal structs do you have any sources going deeper into that?
@zoran-horvat11 ай бұрын
C# reference is a good starting point: learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record You can see from there what exists and then search further for details.
@HOSTRASOKYRA11 ай бұрын
Thank you very much for your explanations!
@zoran-horvat11 ай бұрын
Glad if it was useful.
@HOSTRASOKYRA11 ай бұрын
@@zoran-horvat I hope so too, but the path of pure functional programming in C# still scares me.
@mkama-u5p10 ай бұрын
I would love to use records more but I have one doubt. Even if I just store values that are immutable, I often initialise SOME (but not all) values during creation and update OTHER later. Records don't seem to be in favour in that situation. Or maybe I can do RecordType x = x with {...} to create updated and in fact replace the old object? [I am rather a beginner;)]
@zoran-horvat10 ай бұрын
You are right with your conclusion, but I must ask what is the reason for such practice? I am following the rule that every object must be complete and valid when created. Records are aligned well with that principle.
@mkama-u5p10 ай бұрын
@@zoran-horvat I use API and there is an class (A) with all starting values when object is created. But these values change over life span of this object. I want to create a record that will store both the initial values of (A) and the values when the object is destoryed. Right now I have a class that copy starting values of (A) when (A) is created and copies values of (A) in the moment the object is destroyed. So half of my object values is filled when (A) is created and half when (A) is destroyed. At the end I serialize those values and perform futher statistical analysis on them.
@lluism2004 ай бұрын
I really like the looks of records but how do you manage using them as value objects when inserting data to a database where an identity is required?
@zoran-horvat4 ай бұрын
@@lluism200 If you think of ORM, then it is not meant to handle (immutable) records. Otherwise, it is straightforward. You can include an ID in the record if it corresponds to a database record.
@ДмитрийКондратенко-б5ь11 ай бұрын
10:32, record struct could also be defined with readonly keyword.
@zoran-horvat11 ай бұрын
Yes, I missed the opportunity to add that, too. That is a viable option when there is no validation.
@ДмитрийКондратенко-б5ь11 ай бұрын
Great video!
@ghevisartor624211 ай бұрын
i'm dumb, since i'm using Blazor with JsInterop i had some objects that had to be passed between Blazor and javascript, I used a plain class with the IEquatable, when i could have just used record, i even knew about them. Ahh thanks for the video.
@MC_DarkMaster11 ай бұрын
Would that be an candidate for record struct? If it does make sense how would a record struct look like? public sealed class DateTimeRange { public DateTime Start { get; private set; } public DateTime End { get; private set; } public DateTimeRange(DateTime start, DateTime end) { EnsureStartIsBeforeEnd(Start, End); Start = start; End = end; } static void EnsureStartIsBeforeEnd(DateTime start, DateTime end) { if (start > end) { throw new DateTimeRangeException("The endtime must not be before the starttime"); } } }
@zoran-horvat11 ай бұрын
If you stored DateTime Start and TimeSpan Duration, with DateTime End being a calculated property, then you wouldn't need any validation.
@MC_DarkMaster11 ай бұрын
@@zoran-horvat That is definetly an good option but would it be possible to to convert it to a record struct? And is an struct the right type for that? I find it very hard to decide when it is better to use an record struct instead of an record class
@zoran-horvat11 ай бұрын
@@MC_DarkMaster The decision is the same as with any other type. A record struct is a value type, passed by value and copied on every assignment. A record class is a reference type, assigned and passed by reference. Therefore, you will use the form that you need. If you don't know what you need, then a class will be good enough until you find out.
@cbgrasshopper11 ай бұрын
In a previous video, you recommended against using collections in records due to the issues with deep immutability. Based on this video, it seems you would not have the same concern if the record used frozen collections; is that accurate?
@zoran-horvat11 ай бұрын
Actually, my concern was with equality - collections don't overload Equals and GetHashCode, which are required by the record that might contain them. In that respect, immutable collections and frozen collections are immutable, but still not comparable for equality.
@cbgrasshopper11 ай бұрын
@zoran-horvat In that case, the only option would be a custom type wrapping a collection and implementing the necessary equality members?
@zoran-horvat11 ай бұрын
@@cbgrasshopper The question is what would be the purpose of that. I would rather opt for a common immutable class with no value typed semantics.
@slowjocrow645111 ай бұрын
It seems most objects I deal with have an Id. Is a record still suitable? It doesn't matter the equality of the other properties, only Id. And what about objects with many properties? Is a record slower?
@zoran-horvat11 ай бұрын
A record is just a class. Therefore, it applies in all situations where you would use a class. It is fine to add the ID to a record. You can even watch my next video where I have used a readonly record struct to define a strongly typed ID itself.
@MC_DarkMaster11 ай бұрын
Can you make a video when to use record structs and when to use record classes?
@zoran-horvat11 ай бұрын
The decision is the same as in the case of class vs. struct.
@abj13611 ай бұрын
But I thought the point of classes was pass by reference, which has the point of mutability. Once objects become immutible, the difference seems irrelevant.@@zoran-horvat
@zoran-horvat11 ай бұрын
@@abj136 No it's not. Passing by value is an optimization for types that are small enough so that copying every time a value is used does not cost more than allocation, dereferencing, and freeing combined. For types that are as large as a pointer, the value type may take an order of magnitude less CPU cycles when used.
@Sydra.11 ай бұрын
Too awesome!
@alexhall84011 ай бұрын
Can you give some real world examples on using records. I am struggling to think of where I can use them in the business application I am working on.
@zoran-horvat11 ай бұрын
Quite a few other videos on my channel are using records in domain modeling.
@nickw65611 ай бұрын
The later `record struct` makes the properties mutable by default. Does this seem like a backward trend?
@zoran-horvat11 ай бұрын
No, that's just how structs work. You can declare readonly record struct which is immutable by default.
@1992jamo11 ай бұрын
Your syntax is so absolutely alien to me always, but I find your videos very valuable. Things like var (firstName1, _) = samuelL; do not even look like C# to me. What is the variable here called?
@zoran-horvat11 ай бұрын
That is deconstruction of the record into a tuple, without actually creating a tuple. It stems from an old syntax where you can assign multiple variables in one statement, like (a, b) = (b, a). This will actually swap two values without the use of a third variable.
@1992jamo11 ай бұрын
@@zoran-horvat That is extremely interesting, thanks for explaining. I don't think I'll use it, but I appreciate the lesson.
@alfonsdeda891211 ай бұрын
Hi Zoran, great video! Why class shouldn't derive from another class like in your example with name?
@zoran-horvat11 ай бұрын
Long story :) The short version is that the derived class then carries the unwanted baggage of its base, which may cause all kinds of issues later. Most frequently, it is better to have a clean abstract base which only provides services that are native to every derived type and nothing more. Saving a few lines of code gives little satisfaction when you encounter invalid or dubious assignments later in development.
@alfonsdeda891211 ай бұрын
@@zoran-horvatYes, I misunderstood early, I shouldn't inherit functionality that I don't need but rather work with composition.
@logank.7011 ай бұрын
Everyone should watch out when using the Primary Constructors feature. There is currently no way to mark the backing fields that it creates as readonly. Not a good feature to use in conjunction with Dependency Injection.
@David-id6jw11 ай бұрын
You can't make the class parameters readonly, but you can assign them to readonly fields of the same name, and any use in the class will prefer the field over the parameter, making access to the mutable parameter impossible.
@obinnaokafor625211 ай бұрын
the support is coming in C# 13
@cloudycloudlet709111 ай бұрын
Discord link is not working anymore :(
@zoran-horvat11 ай бұрын
I don't know why "never expire, infinite users" Discord links keep expiring every time I share them... That hurts my programmer feelings. Try the new link now: discord.com/invite/AgPDChDpxg
@halorx986311 ай бұрын
Will you do F# somedays?
@zoran-horvat11 ай бұрын
I have no plans for that at this moment.
@marcotroster82474 ай бұрын
Maybe I'm old school, but I don't like black magic. There's too much implicit behavior to reason about. It's a bit like reflection. Very powerful feature, but you'll never know how a change will impact the calling scope.
@zoran-horvat4 ай бұрын
@@marcotroster8247 I don't think there is any room left for speculation. We already had all that with structs and nobody was ever confused.
@marcotroster82474 ай бұрын
@@zoran-horvat Maybe for enterprisy projects where you don't go into the hardware details it's fine. I've implemented quite some nasty bit hacks with C#, e. g. a card game. Each card is represented by a struct consisting of one byte, with several bitwise features encoded inside those 8 bits. Each player can have 8 cards. So the hand struct is an ulong of 8 bitwise concatenated cards. Then I apply all kinds of SIMD ops and compiler intrinsics to match card features in a hand with bitmasks. I also utilize class variables in the calling scope to avoid stack frame allocation (very fast). Plus I introduce unsafe fixed pointer access to the hand bytes to speed up array copying, etc. Now, would you feel safe to implement the card and hand as record structs when you don't know the exact memory representation? Even little / big endianess might matter. It's not so obvious now, isn't it? C# is quite versatile and your enterprisy way of coding might not perform well. Tbh, when I don't care about performance, I write my service in Python.
@lolyasuo1235Ай бұрын
Records have its place but I'm disappointed to see the community once again (the first time with minimal APIs) trying to re-invent classes. I mean people trying to use class features using records... For example: trying to have properties within records and even worse logic (any logic, validations, overrides etc).
@edkachalov11 ай бұрын
C# invented record from Pascal
@zoran-horvat11 ай бұрын
Nope. What you are referring to as Pascal record is struct in C#. It was borrowed from C, where it existed even before ANSI C and was borrowed from ALGOL where it existed quite some time before the coming of Pascal. The C# record, on the other hand, is something entirely different and it is borrowed from functional programming languages. I hope the matter is clearer now. It is not hard to figure.