Validating Records in C#: Everything You Ever Wanted To Know but Had No One To Ask

Поділитися
Вставка
  • Опубліковано 31 тра 2024
  • Become a sponsor to access source code ► / zoranhorvat
    Join Discord server with topics on C# ► codinghelmet.com/go/discord
    Enroll course Beginning Object-Oriented Programming with C# ► codinghelmet.com/go/beginning...
    Subscribe ► / @zoran-horvat
    This video draws the line under the diverse needs and requests we encounter when enforcing the validity of C# records. You will learn a set of approaches, from no validation at all in proper values, over property assignment validation, all the way to constructor validation of the sort we apply to regular classes.
    There will be talking about record structs, which require double attention thanks to their mandatory default constructor as well as the default keyword that affects their creation.
    There will be a touch of alchemy in between. We will demonstrate compile-time validation, which requires no code to execute at run time. Yet, every record instance will be created valid, with a 100% guarantee.
    You decide which route to take from here. Still, be advised that not all routes are either safe or comfortable. Learn your tools well, and your code will flourish.
    Watch related videos:
    The Ultimate Guide to C# Records ► • The Ultimate Guide to ...
    Possibility of Discriminated Unions in C# ► • Possibility of Discrim...
    00:00 No Validation Case
    04:29 Property Assignment Validation
    07:41 Compile-time Validation
    09:14 Record struct Validation
    11:12 Constructor Validation
    13:48 Summary
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    👨 About Me 👨
    Hi, I’m Zoran, I have more than 20 years of experience as a software developer, architect, team lead, and more. I have been programming in C# since its inception in the early 2000s. Since 2017 I have started publishing professional video courses at Pluralsight and Udemy and by this point, there are over 100 hours of the highest-quality videos you can watch on those platforms. On my UA-cam channel, you can find shorter video forms focused on clarifying practical issues in coding, design, and architecture of .NET applications.❤️
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    ⚡️RIGHT NOTICE:
    The Copyright Laws of the United States recognize a “fair use” of copyrighted content. Section 107 of the U.S. Copyright Act states: “Notwithstanding the provisions of sections 106 and 106A, the fair use of a copyrighted work, including such use by reproduction in copies or phono records or by any other means specified by that section, for purposes such as criticism, comment, news reporting, teaching (including multiple copies for classroom use), scholarship, or research, is not an infringement of copyright." This video and our youtube channel, in general, may contain certain copyrighted works that were not specifically authorized to be used by the copyright holder(s), but which we believe in good faith are protected by federal law and the Fair use doctrine for one or more of the reasons noted above.
    #csharp #dotnet #objectorientedprogramming
  • Наука та технологія

КОМЕНТАРІ • 52

  • @nocturne6320
    @nocturne6320 2 місяці тому +7

    Great and helpful video, especially the idea of NonEmptyStrings :)
    One thing to note is that at 13:50 the PositiveMoney example could've used a private static method for validation of the parameter, it would keep the code cleaner by not cluttering the primary constructor with validation one-liners and it would also allow for more complex validation to take place, if it had to span multiple lines

  • @Mig440
    @Mig440 2 місяці тому +4

    I think there is one escape hatch for bugs to come in with your example of NonEmptyString. The copy constructor automatically generated by the compiler allows for an expression like the following to both compile and run without exceptions thrown:
    var invalidNonEmptyString = nonEmptyString with { Value = ""}
    This should perhaps be discussed how to proceed in this case to prevent such programming errors from propagating.

    • @zoran-horvat
      @zoran-horvat  2 місяці тому +2

      You are right. I might add an analysis of this case in a later video. It is very interesting.

    • @goldmund67
      @goldmund67 2 місяці тому

      I think you already addressed that scenario in your recent video about records by making the Value a read only property set to the Value from the positional constructor parameter?

    • @Mig440
      @Mig440 2 місяці тому +1

      @@goldmund67
      That is exactly what zoran did here, but that wont resolve the problem because the copy constructor does not use the property initializer as shown in the video.

    • @Mig440
      @Mig440 2 місяці тому

      The fundamental issue seems to be that because it is impossible to get at the new value of the encapsulated string

  • @jonobaker
    @jonobaker 2 місяці тому +3

    Really appreciate your videos, and I find them really handy for passing to colleagues, thank you.

    • @zoran-horvat
      @zoran-horvat  2 місяці тому

      Glad you like them! I appreciate your support.

  • @ahjsbkdjhavkcjhvac
    @ahjsbkdjhavkcjhvac 2 місяці тому +2

    amazing video Mr. Zoran!! very insightful!!

  • @johncerpa3782
    @johncerpa3782 2 місяці тому +4

    It will hurt but you asked for it 😂😂

  • @Buutyful
    @Buutyful 2 місяці тому +1

    hey Zoran, i was looking on youtube for a good source of truth about how to implement ef relationships but i didnt find much! i'd love some content on that and i think many other could benefit from it :D !

  • @MGinx
    @MGinx 2 місяці тому

    Very informative videos, thank you.
    I would be curious as to your thoughts on using records in option pattern and if you would even consider using it (given the numerous ways options can be binded and configured) and if so, would you still take the same approach to apply validation?

    • @zoran-horvat
      @zoran-horvat  2 місяці тому

      Records are useful in options, though I would refrain from letting the records validate the options.

  • @emadabushofa2379
    @emadabushofa2379 2 місяці тому +1

    I hope that there is an easy way to tell the json serializers how to serialize these custom types

  • @johncerpa3782
    @johncerpa3782 2 місяці тому +1

    Great video

  • @kostasgkoutis8534
    @kostasgkoutis8534 2 місяці тому +1

    Wow, the Currency record struct was a landmine 💣.

  • @unisonrul1171
    @unisonrul1171 2 місяці тому +1

    Very good

  • @ghevisartor6005
    @ghevisartor6005 19 днів тому

    Sorry but i lack a bit of experience on this topic. Are the models like Person and Tag supposed to be on their own or can they be used as complex types for EF core?
    I guess they are the model that is core tho the app and it is then used to populate entities?
    For example if i try to use them and migrate EF fail since it cant bind Name and Surname to properties on the Person type, i think there are workarounds for it but is logically wrong?

    • @zoran-horvat
      @zoran-horvat  19 днів тому

      Records are not of great use with EF Core because they are immutable by default. Making them mutable requires the same level of effort as writing a common class, which defeats the idea of records.
      Records are primarily used in functional designs, where they become the principal design element together with delegates and static methods.

    • @ghevisartor6005
      @ghevisartor6005 19 днів тому +1

      @@zoran-horvat thanks! i'm dealing with Blazor and making a non trivial model, thanks to some of your courses i realized that much of my code is procedural rather than oop or functional, i'm slowly trying to move to away from it, but it's kinda hard to fit it all together

  • @szymonlisiecki4452
    @szymonlisiecki4452 2 місяці тому

    Great video!
    How would you handle the situation when the validity of the record depends on the relationship within its fields? Example:
    record SchoolStage(SchoolType schoolType, short gradeNumber);
    A school stage representing the 6th grade of primary school is valid, but the 6th grade of high school shouldn't be. Should this validation with all potential cases verification be implemented in the constructor?

    • @zoran-horvat
      @zoran-horvat  2 місяці тому +1

      Why not have a discriminated union with one type per school type?

  • @matheosmattsson2811
    @matheosmattsson2811 2 місяці тому

    7:06 row 3-7 had me confused for a bit. Primary constructors... :P The use of the variable "Value" was ambiguous to my eyes, but not to the compiler. It is used both in the primary constructor and as a parameter name, in the same exact form. I prefer using lowercase parameter names in the primary constructor to avoid using the exact same name and capitalization as in the corresponding Property name. This only though if the Record has a body, if it lacks a body I would opt for capitalized parameter names.
    Good video!

    • @zoran-horvat
      @zoran-horvat  2 місяці тому +1

      It is standard to capitalize parameters in the record's primary constructors because they are also properties. But I agree that it is confusing.

  • @Alguem387
    @Alguem387 2 місяці тому

    besides default there is the empty struct constructor witch is always public, you can only get rid of it with an attribute [Obsolete(true, "There be dragons here")]

  • @gavincampbell1061
    @gavincampbell1061 2 місяці тому +5

    Having init on the Value of the NonEmptyString record allows the validation to be bypassed.
    new NonEmptyString("valid") { Value = string.Empty }; will not cause the exception to be thrown and the Value will be empty.

    • @zoran-horvat
      @zoran-horvat  2 місяці тому

      Yes, you are right. There is also the impact on with expressions that should be evaluated.

    • @gavincampbell1061
      @gavincampbell1061 2 місяці тому +1

      @@zoran-horvat If init is removed, that prevents with being used as Value is now read only, and prevents Value being set in the initializer list. It's a possible fix.

    • @zoran-horvat
      @zoran-horvat  2 місяці тому +5

      That is what I had in mind. I might spare part of some future video to analyse and demonstrate those effects. Thank you for the reminder!

    • @franciscorusso8062
      @franciscorusso8062 2 місяці тому

      @@gavincampbell1061 I would do it this way. It would enforce the validation on every initialization case and avoid nondestructive mutation that could bypass the validation. From my point of view, nondestructive mutation in a value doesn't make any sense, if you want a new value, just create a new one from scratch

  • @C00l-Game-Dev
    @C00l-Game-Dev Місяць тому

    Another way you could turn strings to non empty strings could be an extension method. Like Name.NonEmptyString();

    • @zoran-horvat
      @zoran-horvat  Місяць тому

      You cannot turn a null or empty string to a non-empty one, unless the caller supplies the non-empty value with the call.

  • @7th_CAV_Trooper
    @7th_CAV_Trooper 2 місяці тому

    Maybe I don't get the point of record then. I think you're saying they're value objects. But why would I model value in a garbage collect heap object?
    Love that behavior by inheritance idea.

    • @zoran-horvat
      @zoran-horvat  2 місяці тому +1

      I think the confusion is between value typed and value objects. A record can either be a value type (record struct) or a reference type (record class). When you only state 'record' it is a record class, i.e. a reference type.
      Value object is a pattern regarding a type which implements value-typed semantics (by overriding Equals and GetHashCode in C#). A value object can either be a value type or a reference type.

  • @Buutyful
    @Buutyful 2 місяці тому

    should you have the same approach with classes also?

    • @zoran-horvat
      @zoran-horvat  2 місяці тому +1

      Classes tend to model larger parts of the domain and hence we often rely on heavy constructor validation and even on external validators.
      However, as more and more of the functional programming practices are finding their place in C#, the impact of small, composable models, such as those represented with records, becomes more visible and more widely applied in domain modeling.

  • @AndersBaumann
    @AndersBaumann 2 місяці тому

    Using exceptions in validation code will throw a lot of exceptions in a large system. They will clutter the application monitoring and hide the actual problems. In addition, exceptions are expensive.
    Invalid user input is expected.

    • @zoran-horvat
      @zoran-horvat  2 місяці тому

      The constructor cannot return any results, hence exceptions and assertions are the only way to indicate error. Error returns are possible from smart constructors and factory methods.

    • @AndersBaumann
      @AndersBaumann 2 місяці тому +1

      Make the constructor private and add a static factory method that returns a Result.

    • @zoran-horvat
      @zoran-horvat  2 місяці тому

      @@AndersBaumann That's what I said. However, that would make 90% of the audience leave this video before the end, which, in turn, is why I have made the video in smart constructors separately.

    • @AndersBaumann
      @AndersBaumann 2 місяці тому

      @@zoran-horvat OK. But it would be good to mention the tradeoffs.

    • @zoran-horvat
      @zoran-horvat  2 місяці тому

      @@AndersBaumann Sorry, not in this video. The audience is too picky and I'm not making videos to scare them away...

  • @dave_s_vids
    @dave_s_vids 2 місяці тому +1

    The first rule of Record validation club is don’t

  • @MasterKrzychuu
    @MasterKrzychuu Місяць тому

    I think this short's title is misleading. The with keyword didn't evade validation, because there is nothing to evade.
    The only validation is on the property initializer, none on the setter. You can "evade" it the same way by writing new NonEmptyString("Hi") { Value = "" };, even if it's a normal class - nothing to do with record or the with keyword.
    You exposed an init setter with no validation, so you predictably get undesirable results when you use it. (You correctly explain the solution in the video, but I don't see why you point out records or with in particular, when it's not caused by either thing).

    • @zoran-horvat
      @zoran-horvat  Місяць тому

      The short video was about with expressions specifically, not about classes with init setters. The rest is with expression's implementation imposed by the compiler.
      It is certainly possible to introduce validation in the init setter, including the explicit backing field but... will anybody ever do that? In a record - no.
      Hence the conclusion - with expressions evade the primary constructor and therefore evade validation; disable with expressions for properties of primitive types and force all changes through the primary constructor.

  • @lazarnikolic8161
    @lazarnikolic8161 2 місяці тому

    What are your thoughts on using records in tandem with other immutable structures for modeling the entire domain, if the goal is a fully immutable model? Would you say it is still inadvisable? Achieving this with "standard" language features is, of course, painful. @zoran-horvat

    • @zoran-horvat
      @zoran-horvat  2 місяці тому +3

      There are usually no obstacles to modelling an entire business domain using only immutable structures and types. That is what we normally do in any pure functional language.
      There are two areas that require special attention. CPU-bound algorithms often require mutability, or otherwise their runtime performance may be prohibitively low. Take sorting as an example: There is no functional sorting algorithm, only the procedural ones. We address this problem by wrapping mutable/procedural algorithms into functions that satisfy the norms of functional programming on the outside. The other problem is persistence, where we must approach the mutable database differently compared to what we do in object-oriented code based on mutable entities.
      However, these issues do not make functional programming any less potent than object-oriented. They make it different, but that alone is not an issue.

    • @lazarnikolic8161
      @lazarnikolic8161 2 місяці тому +2

      ​@@zoran-horvatThank you for a detailed reply. Yes, the reason I ask is precisely because I'm actively trying to apply a more functional approach to C# programming, and although I found that domain modeling using records does get a bit awkward, it is far superior to class-based immutability.
      I'm obviously talking about business applications, where performance isn't as critical of a factor, LINQ does the sorting, and persistence is usually delegated to a dedicated class-based model anyway.