Download the source code from Patreon: www.patreon.com/posts/source-code-for-88717639 Learn more from the video course *Beginning Object-Oriented Programming with C#* ► codinghelmet.com/go/beginning-oop-with-csharp Learn from related videos: Clean Code Tip: Remove Messy Constructor Calls ► th-cam.com/video/6g4ggpOxxCc/w-d-xo.html Remove Separate Concerns From a Class and Make It Favor SRP Again ► th-cam.com/video/5CU7137IWf4/w-d-xo.html Avoid Returning Null From Methods - There Is a Better Way To Write Them! ► th-cam.com/video/HRLdcMil7Ec/w-d-xo.html
I really like using extension methods, construct human readable chain of calls and make my code like a prose describing a feature :) Thank you for spreading the technique, Zoran 🥰
So, method chaining improves code readability by allowing operations to be performed in a clear, linear sequence. The Single Responsibility Principle, on the other hand, enhances maintainability by ensuring that each class has a distinct responsibility, moving related methods into separate classes as needed. Thank you Zoran
Nested calls look like a sock turned inside out; that's great. ZH, love the format of your videos... showing how to improve working code with better design really helps demonstrate the underling benefits of employing patterns.
Fantastic video, started by thinking that it's quite usual to solve things with the initial version, when you arrived at the final version I was really pleasantly suprised! Awesome!
«Yoda Speaking» 😂. Your contents are becoming attractive not only for the design lessons distilled, you recall me a Finnish comedian. Really enjoyable your videos 🎉
Great tips! It is very important to know that you spend much more time reading the code compared to writing it, so it is very important that it is readable. If you're having to have additional mental mapping to read code "backwards", it only uses up your concentration and downgrades what you write even further. Now, for the Star Wars fans, let me try to sum this video up in one sentence. Write code as if Master Yoda speaks you should not.
That's fun. I recently did a fuzzy controller for a project of mine and attempted to do a fluent syntax. Although I'm not really used to writing this kind of code but it seemed like a fun challenge and I think it came out well!
This usage of extension methods is great. I try to apply it, when possible, but I always find it challenging when you need to use multiple parameters (typical in async flows with the cancellation tokens). C# partial application support is not nice (to put it is good words), hope you have some video ideas around this scenario for the future!
Main point is that you should just write your business method in valid C# the way you want first so that it expresses business requirement and then just implement what you’ve written
Looks really nice! However, what if those are instance methods (non-static)? I can see two options: either make them static and pass `this` param explicitly, or use some sort of `|>`-like construction (that's the pipeline operator in F#), maybe something like `TO FeedTo(this TI value, Func f) => f(value);`, then you can chain anything without converting things into extensions, may look ugly sometimes though if you have additional args to close over, e.g.: `12.FeedTo(calc.Log).FeedTo(v => calc.Mul(v, 42)).FeedTo(calc.Sqrt)`. Plays more nicely with curried functions: `53.FeedTo(calc.Mul(42)).FeedTo(calc.Add(1))`
C# has no syntactic support for partial function application. All it supports is the dot operator for a narrowed-down kind of function application. Being so, I am not too motivated to push it any further.
@@zoran-horvat well, at least it supports first-class functions, hence currying. The currying doesn't play nice in C# with type inference, brings cumbersome types with it and is generally slower, so not the recommended practice, agreed
I wonder if there are any languages that implement function calls as postfix operators, kind of like Smalltalk but without OOP. So you'd do 123.sqrt() instead of sqrt(123). Anyway, great vulgarization work as always. You explain things really well in a way that's easy to understand. Your videos turn coders into programmers.
Actually, functional languages cater that kind of notation. F# uses pipe-forward operator |> for that purpose. Combined with currying, it becomes a truly powerful concept.
@zoran-horvat How would you approach testing the functionality in your example? Which part would you test and how? Which parts do you think is not necessary to test? Thank you in advance for your answer.
Each individual method is a potential unit if you wish to test it. A method that uses chainable methods is also one unit. Its implementation is the detail you do not test. Every claim you wish to make about any of the methods is one test.
They are not the same thing. An extension method applies to a type but does not change it. I generally prefer functional extensions because I can organize them in per-domain namespaces, which is not the case when changing the base type whenever any domain that depends on it changes. You can view that design practice as a reinforcement of the SRP.
I think not, but that class is very simple (as any other is, in this design). It holds two IDiscounts, asks the first one and, if it returns a non-empty sequence, returns that sequence and ignores the other IDiscount. Otherwise, if the first sequence is empty, it just returns the second sequence. The implementation is very similar to how LINQ DefaultIfEmpty is implemented - hence the name.
Well thanks for the video. My personal opinion is, that I would have stopped before the introduction of "OrElse". I find the "FirstOf()" outer call clearer, because all three calls inside are on the same level. With OrElse on the other hand, the first call is outside the OrElse method call, but the other two inside OrElse. I find that ugly. But that’s probably just my opinion. For the same reason I prefer "Enumerable.Zip(a,b,…)" instead of "a.Zip(b, …)". (Usually with line breaks between the parameters.)
Or if you're an FP dev, you can use the .Pipe() call, pass the arguments, all the methods you need in the pipeline and run it. It's the cleanest approach IMCO.
When there are many calls involved, then some of them naturally form meaningful groups that deserve a name and a method of their own. Having more than a couple of calls in a row already begins to look suspicious. Think of it as a SQL query with SELECT, FROM and WHERE. Consider a LINQ expression with the sequence, a call to Where, possibly Select, and then followed by a reduction operator. Anything else, with all its complexity, can be a function passed as the argument, reducing complexity by a large margin.
my problem with the expression is that it is hard for debugging. Let's say something wrong in the GetDiscounts, I don't know how to investigate the issue. At that time, I have to rewrite the code and create some variables to separate each call
@@zoran-horvat I meant your video is great, I definitely try to write as you do in the video. I just say that I try to avoid expressions, nested functions, etc. in general because it's hard to debug. I just use them if the code is extremely clear and I would never need to investigate it in the future. I just don't want to see the whole source code with expressions and everytime I need to check something, I need to stop the debugger, rewrite the code to expose some variables and start the debugger again
@@minhhieugma I agree. Also, ideally, code is written once, but read multiple times. I will happily give up brevity to get clarity. It also saves me from having to answer the Junior devs question about what the code does. I do not think writing terse code should be the goal. Clear code is better.
It becomes difficult when you need to vary the implementation. It would be a virtual method or a dependency interface in a common class, but with functions, we are back at nested calls.
@@zoran-horvat Do you know how I can do that? When I click the Patreon link, I have to click upgrade to unlock, and the only options are recurring fees.
Extension methods are great but mocking static methods for unit testing may not be ideal. Another way to do this is to just use a regular class that is not static and return the object itself on every method call. Which is what is typically used when creating Fluent APIs.
Extension methods are like any other static methods. If a caller needs varying behavior, it would ask for a Func delegate, rather than depend on an extension method. Therefore, extension methods are no obstacle to testing.
I wish C# just used a void return as returning the instance that the call was made from. Then builder methods would've been a lot easier to make and not need extension methods in a separate class.
@@zoran-horvat I'm hoping something like this will come to a future C#. Maybe using 'this' as a return keyword. e.g. public this WithName(string name) { ... }.
I was like please don’t resolve to extensions methods please please. I mean I love them and use them all the time but this was presented as a architectural problem … syntactic sugar might not be a solution
Method chaining is pretty much requiring you to implement your helper functions into a OOP way. I guess for general simple cases without thinking about OOP or adding unnecessary abstractions. The easiest way is just to break your nested calls into multiple lines. Easy to read and work for both fp and oop. ``` a = methodA(); b = methodB(a); c = methodC(b); ``` But once you realized that you will have quite amount of continuous operations upon a value. Method chaining is your good friend. Just don't abuse it everywhere.
I believe that |> operator will not be added, because it would require too many changes in C#. In F# |> is a function which relies on higher-order functions.
Why did I write higher-order function? I meant partial application, of course. I believe that implementing partial function application in C# would be very difficult.
I am struggling to understand your train of thought. What is the point with knowing what the last step will do? You don't know what the immediate next step will do for the same reasons, and I don't see you calling **that** evil. My view of CoR (and any other indirection for that matter) is that it will do what its interface commands, as in the Design by Contract, to put it formally. If there is any gap in logic there, then the definition of the interface is incomplete.
@@stefanotorelli3688 I believe you should reconsider your programming method from the ground up. It is very difficult to try to persuade you against practices that were abandoned by the end of 1990s.
@@zoran-horvat Patterns have to be used when needed. If you use them but after 2 years u left the company and nobody is going to understand what you did... who pay that? My programmming methods? lol My phylosophy: use the patterns when needed. Otherwise do not use them. Sorry if for you it's 90's but your approach is 90's more than me. And I will unsuscribe fro your channell. lol.
Download the source code from Patreon: www.patreon.com/posts/source-code-for-88717639
Learn more from the video course *Beginning Object-Oriented Programming with C#* ► codinghelmet.com/go/beginning-oop-with-csharp
Learn from related videos:
Clean Code Tip: Remove Messy Constructor Calls ► th-cam.com/video/6g4ggpOxxCc/w-d-xo.html
Remove Separate Concerns From a Class and Make It Favor SRP Again ► th-cam.com/video/5CU7137IWf4/w-d-xo.html
Avoid Returning Null From Methods - There Is a Better Way To Write Them! ► th-cam.com/video/HRLdcMil7Ec/w-d-xo.html
I really like using extension methods, construct human readable chain of calls and make my code like a prose describing a feature :)
Thank you for spreading the technique, Zoran 🥰
So, method chaining improves code readability by allowing operations to be performed in a clear, linear sequence. The Single Responsibility Principle, on the other hand, enhances maintainability by ensuring that each class has a distinct responsibility, moving related methods into separate classes as needed. Thank you Zoran
Nested calls look like a sock turned inside out; that's great. ZH, love the format of your videos... showing how to improve working code with better design really helps demonstrate the underling benefits of employing patterns.
Fantastic video, started by thinking that it's quite usual to solve things with the initial version, when you arrived at the final version I was really pleasantly suprised! Awesome!
My journey through this video...
2:00 Cool, cool.
5:20 Yep! Seen this a lot, yoda has.
8:42 MIND COMPLETELY BLOWN 🤯
«Yoda Speaking» 😂. Your contents are becoming attractive not only for the design lessons distilled, you recall me a Finnish comedian. Really enjoyable your videos 🎉
Best series in whole YT!
Thanks!
For all the great content you upload you should have 1 million sub!
Great tips! It is very important to know that you spend much more time reading the code compared to writing it, so it is very important that it is readable. If you're having to have additional mental mapping to read code "backwards", it only uses up your concentration and downgrades what you write even further.
Now, for the Star Wars fans, let me try to sum this video up in one sentence. Write code as if Master Yoda speaks you should not.
this is was super helpful! i'm applying the technique in PHP Laravel. Good methodologies aren't bound by language
Wow, amazing video.
+ your humor is sooo good.
Informative and fun at the same time 😂😂
I hit the like button even before you said it. OMG
Wow, I started watching this video with quite scepticism but the end result is 10x quality of the initial solution. Thanks sir
I'm starting to get it. going back to look at my ball of mud to see what I can improve.
That's fun. I recently did a fuzzy controller for a project of mine and attempted to do a fluent syntax. Although I'm not really used to writing this kind of code but it seemed like a fun challenge and I think it came out well!
I discovered your channel only recently, your videos are great and show some great principles, thank you and keep up the great work
Thanks!
This is so good. I've always wondered how chaining like this works, and can't wait to try and implement this in my own code.
This usage of extension methods is great. I try to apply it, when possible, but I always find it challenging when you need to use multiple parameters (typical in async flows with the cancellation tokens). C# partial application support is not nice (to put it is good words), hope you have some video ideas around this scenario for the future!
Thanks you :), this replies to the exact question I posted in the previous video
Main point is that you should just write your business method in valid C# the way you want first so that it expresses business requirement and then just implement what you’ve written
Looks really nice! However, what if those are instance methods (non-static)? I can see two options: either make them static and pass `this` param explicitly, or use some sort of `|>`-like construction (that's the pipeline operator in F#), maybe something like `TO FeedTo(this TI value, Func f) => f(value);`, then you can chain anything without converting things into extensions, may look ugly sometimes though if you have additional args to close over, e.g.: `12.FeedTo(calc.Log).FeedTo(v => calc.Mul(v, 42)).FeedTo(calc.Sqrt)`. Plays more nicely with curried functions: `53.FeedTo(calc.Mul(42)).FeedTo(calc.Add(1))`
C# has no syntactic support for partial function application. All it supports is the dot operator for a narrowed-down kind of function application. Being so, I am not too motivated to push it any further.
@@zoran-horvat well, at least it supports first-class functions, hence currying. The currying doesn't play nice in C# with type inference, brings cumbersome types with it and is generally slower, so not the recommended practice, agreed
I wonder if there are any languages that implement function calls as postfix operators, kind of like Smalltalk but without OOP.
So you'd do 123.sqrt() instead of sqrt(123).
Anyway, great vulgarization work as always. You explain things really well in a way that's easy to understand. Your videos turn coders into programmers.
Actually, functional languages cater that kind of notation. F# uses pipe-forward operator |> for that purpose. Combined with currying, it becomes a truly powerful concept.
Great video! Keep up the great work. It is really teaching me a lot about modern design language!
What a great demo! Thank you!
@zoran-horvat How would you approach testing the functionality in your example? Which part would you test and how? Which parts do you think is not necessary to test? Thank you in advance for your answer.
Each individual method is a potential unit if you wish to test it.
A method that uses chainable methods is also one unit. Its implementation is the detail you do not test.
Every claim you wish to make about any of the methods is one test.
@@zoran-horvat Thank you.
Great video! Keep up the great work
Subscribed! I found this very insightful, thank you.
Why would you prefer extension methods instead of methods on an abstract base?
They are not the same thing. An extension method applies to a type but does not change it.
I generally prefer functional extensions because I can organize them in per-domain namespaces, which is not the case when changing the base type whenever any domain that depends on it changes.
You can view that design practice as a reinforcement of the SRP.
Is there any video where you implemented the "DefaultIfEmptyDiscount" class?
I think not, but that class is very simple (as any other is, in this design). It holds two IDiscounts, asks the first one and, if it returns a non-empty sequence, returns that sequence and ignores the other IDiscount. Otherwise, if the first sequence is empty, it just returns the second sequence. The implementation is very similar to how LINQ DefaultIfEmpty is implemented - hence the name.
Something like this?
return _other.GetDiscountAmounts(price, context).Any()
? _other.GetDiscountAmounts(price, context)
: _alternative.GetDiscountAmounts(price, context); @@zoran-horvat
Love it. Thanks for the video.
Great video, thank you!
Well thanks for the video.
My personal opinion is, that I would have stopped before the introduction of "OrElse". I find the "FirstOf()" outer call clearer, because all three calls inside are on the same level. With OrElse on the other hand, the first call is outside the OrElse method call, but the other two inside OrElse. I find that ugly. But that’s probably just my opinion.
For the same reason I prefer "Enumerable.Zip(a,b,…)" instead of "a.Zip(b, …)". (Usually with line breaks between the parameters.)
Or if you're an FP dev, you can use the .Pipe() call, pass the arguments, all the methods you need in the pipeline and run it. It's the cleanest approach IMCO.
Beautiful!
Do you see any reasonable limit to method chaining? I mean, when I get to 15 method-chains I start to second-guess myself...
When there are many calls involved, then some of them naturally form meaningful groups that deserve a name and a method of their own. Having more than a couple of calls in a row already begins to look suspicious.
Think of it as a SQL query with SELECT, FROM and WHERE. Consider a LINQ expression with the sequence, a call to Where, possibly Select, and then followed by a reduction operator. Anything else, with all its complexity, can be a function passed as the argument, reducing complexity by a large margin.
my problem with the expression is that it is hard for debugging. Let's say something wrong in the GetDiscounts, I don't know how to investigate the issue. At that time, I have to rewrite the code and create some variables to separate each call
Modern debuggers are helping reduce that problem a lot. How would you debug nested calls otherwise, if not through the process you just mentioned?
@@zoran-horvat I meant your video is great, I definitely try to write as you do in the video. I just say that I try to avoid expressions, nested functions, etc. in general because it's hard to debug. I just use them if the code is extremely clear and I would never need to investigate it in the future.
I just don't want to see the whole source code with expressions and everytime I need to check something, I need to stop the debugger, rewrite the code to expose some variables and start the debugger again
@@minhhieugma I agree. Also, ideally, code is written once, but read multiple times.
I will happily give up brevity to get clarity. It also saves me from having to answer the Junior devs question about what the code does.
I do not think writing terse code should be the goal. Clear code is better.
Isnt it the same as Fluid Interface? What are cons of such technique? When not to use it?
It becomes difficult when you need to vary the implementation. It would be a virtual method or a dependency interface in a common class, but with functions, we are back at nested calls.
I'd like this source code, but I don't want to pay a recurring monthly fee to get it. Can I pay a one-time fee and get it?
@@rhornjr Yes, you can.
@@zoran-horvat Do you know how I can do that? When I click the Patreon link, I have to click upgrade to unlock, and the only options are recurring fees.
@@rhornjr You should be able to cancel the subscription if there are no other options supported.
Extension methods are great but mocking static methods for unit testing may not be ideal. Another way to do this is to just use a regular class that is not static and return the object itself on every method call. Which is what is typically used when creating Fluent APIs.
Extension methods are like any other static methods. If a caller needs varying behavior, it would ask for a Func delegate, rather than depend on an extension method. Therefore, extension methods are no obstacle to testing.
I wish C# just used a void return as returning the instance that the call was made from. Then builder methods would've been a lot easier to make and not need extension methods in a separate class.
Sounds interesting, but I think C# is built on more explicit rules.
@@zoran-horvat I'm hoping something like this will come to a future C#. Maybe using 'this' as a return keyword. e.g. public this WithName(string name) { ... }.
That sounds suspiciously like FP
Prelepo!
I was like please don’t resolve to extensions methods please please. I mean I love them and use them all the time but this was presented as a architectural problem … syntactic sugar might not be a solution
Method chaining is pretty much requiring you to implement your helper functions into a OOP way. I guess for general simple cases without thinking about OOP or adding unnecessary abstractions.
The easiest way is just to break your nested calls into multiple lines. Easy to read and work for both fp and oop.
```
a = methodA();
b = methodB(a);
c = methodC(b);
```
But once you realized that you will have quite amount of continuous operations upon a value. Method chaining is your good friend. Just don't abuse it everywhere.
This is why C# should add the pipe operator |>
I believe that |> operator will not be added, because it would require too many changes in C#. In F# |> is a function which relies on higher-order functions.
Why did I write higher-order function? I meant partial application, of course. I believe that implementing partial function application in C# would be very difficult.
@@fsharpfan Pipe operator could easily be done in c# by lowering it to use locals. There just doesn't seem to be enough desire for it.
CoR! It's hell for the common needs! You delegate but you will never know what the last step will do! That's evil!
I am struggling to understand your train of thought. What is the point with knowing what the last step will do? You don't know what the immediate next step will do for the same reasons, and I don't see you calling **that** evil.
My view of CoR (and any other indirection for that matter) is that it will do what its interface commands, as in the Design by Contract, to put it formally. If there is any gap in logic there, then the definition of the interface is incomplete.
@zoran-horvat when I need to debug them... it will work... nope.. if I need to debug nothing is working
@@stefanotorelli3688 I believe you should reconsider your programming method from the ground up. It is very difficult to try to persuade you against practices that were abandoned by the end of 1990s.
@@zoran-horvat Patterns have to be used when needed. If you use them but after 2 years u left the company and nobody is going to understand what you did... who pay that?
My programmming methods? lol
My phylosophy: use the patterns when needed. Otherwise do not use them. Sorry if for you it's 90's but your approach is 90's more than me. And I will unsuscribe fro your channell. lol.
The implementation done here is Yoda speaking style :D
en.wikipedia.org/wiki/Specification_pattern#C#