Cleaner C# code with "smart" using statements
HTML-код
- Опубликовано: 6 ноя 2024
- Check out my Dependency Injection course: dometrain.com/...
Use discount code YTDEP1 at checkout for 15% off
Become a Patreon and get source code access: / nickchapsas
Hello everybody I'm Nick and in this video I will show you how to use the using statement in C# in a different way in order to write cleaner C# code in .NET.
Don't forget to comment, like and subscribe :)
Social Media:
Follow me on GitHub: bit.ly/ChapsasG...
Follow me on Twitter: bit.ly/ChapsasT...
Connect on LinkedIn: bit.ly/ChapsasL...
Keep coding merch: keepcoding.shop
#csharp #dotnet
We have pretty much exactly this class in our code base. And while I don't mind it for that particular use case, I'd be careful in introducing that in other parts of the code.
Mainly because it is already hard enough to get junior devs to understand that the garbage collector is not magic and that to prevent memory "leaks" IDisposable is a thing and needs to be looked out for and adhered to.
And having a ton of classes that all implement IDisposable even though they don't actually dispose of any resources just makes it more likely that people will stop checking for IDisposable (or ignore IDE hints about a missing Dispose) and therefore miss the cases where it is absolutely necessary to call Dispose and then you're left diagnosing why your application ballooned to gigabytes in size on the customer's machine.
So technically a nice trick but since it is a misuse of the Dispose pattern, it should be used very sparingly for the kinds of classes where you'd need to litter half the code base with finally blocks without it.
Another quirk of being a "misuse", is that framework upgrades doesn't have to comply to it or even mark the use as deprecated for future removal. Meaning, if the community decides to change how it works, you can have massive problems upgrading to new versions. Chances are small, but they do exist. Still a very fun trick though. I have to admit.
Great comment, I learned from it!
Cool technique in some cases though, hadn't considered this usage.
I was looking for this exact point of view in the comment section and was surprised it's the first comment. That's exactly what I thought about. A nice trick but not a nice choice.
So much this. As much as it's kind of a nice party trick, it's really a bad idea long term to implement it that way.
Ive been on teams where the mindset was, "dont let the jr devs break anything" by using restrictions or dumbing down things. And on teams where, "if the jr devs break something they own fixing it".
I beleive the first is better for financially critical systems like commercial software or literal financial software, and the later is better for inhouse supported and built software, like custom ms excel upload UIs to be processed by commercial software.
I like this. We used to do something similar in C++ using the destructor at a job I had about 15 years ago. In C++, you can declare a local instance of a class without using the new keyword and it allocates it using stack memory and automatically disposes when the variable goes out of scope. We definitely used it for timing operations like this. I seem to recall we also used it to acquire and release a lock or something because C++ didn't have the lock keyword.
IDisposable + using brings to the table the equivalent of C++ destructors. In other words, you get the ability to guarantee that a block of code will automatically get executed when a variable falls out of scope, without having to clutter the code with more elaborate bootstrapping that distracts from the responsibility of the unit of code that you're writing. And while this is an incredibly powerful tool to have around, it comes with the caveat that in both cases, these mechanisms were designed with the intent to prevent human error regarding freeing up resources acquired by the object in question. You're not supposed to be doing anything other than releasing resources in this automatically executed code.
That being said, this design intent is something I tend to abandon very quickly in all my projects. Rather than limiting IDisposable implementations to classes that free resources, I instead consider IDisposable implementations as classes that delegate code execution on object disposal by design because this is the feature that the language actually provides. I can use this in principle to free acquired resources, but also to run an arbitrary unit of code that might be interesting to have.
Is this a problem for me? No, because I always check for IDisposable implementations. It doesn't matter how IDisposable is implemented in any given class because the intent is always the same: when the object is no longer necessary, it should be disposed. How this disposal manifests itself should not be a concern of the code that uses the disposable object in the first place. As such, it doesn't matter if Dispose actually releases resources because I would still look to ensure it gets called in client code.
Thanks again Nick, I truly love your curiosity and the fact that you are sharing.
It's inspiring to watch someone who enjoys his work. My Man
The same can be done to create a sort of lock declaration. Just decompose the lock keyword to monitor.enter() and monitor.exit(). Or even with SemaphoreSlim with IAsyncDisposable. For example: await using var _ = _ss.WaitAsyncScoped();
Or using var _ = _locker.LockScoped()
I know c# to the core, but I still enjoy watching you videos :)
That's really cool! I never knew `using` worked that way. Love how you deep dived into it and actually showed what happens under the hood.
9:22 gets me every time. 😂😂😂😂
An elegant way of leveraging the using statement mechanics, thanks!
Yeah so it is some kind of defer in Go. You can make it generic and pass any delegate to be executed. There is a good article about this called "Defer with C# 8.0"
It’s so smart and tricky at the same time… I wouldn’t mind having something similar in a closed package.
Why closed? Because of the confusion it brings to the table.
When Dispose() is not disposing anything, then it’s not supposed to be named like this.
On the other hand, it’s so clean… maybe having a forwarding layer over IDisposable would be less confusing (also giving the opportunity to document this abuse of IDispoable were I think is relevant)
I recommend using it in library code only since you can easily hide it and no one will ever know. In main project code, developers that don’t know how this works will be lost
You can use the destructor instead and I think that's less confusing and avoids hijacking IDisposable. This will be familiar to you if you come from C++.
Except that you can’t really decide when the destructor is called. It’s used by the garbage collector and you can’t explicitly call it so your measurement will be wrong most of the time.
IMO it's a perfectly legit use-case. I think it's a little infantilizing of one's fellow devs to think that they wouldn't be able to grok it.
It also comes with the added benefit that the team will end up understanding disposables/usings a little better as a result.
You can have an explicit IDisposable.Dispose() method and return the TimingLogger from the extension method. It'll still show up as IDisposable so "using" works, but you have to cast it to IDisposable to call Dispose manually.
That’s exactly the pattern used for MiniProfiler ! It’s a good example of production ready use of the concept explained here
never thought of using "using" like that. Love it :)
I recently used a similar approach. But instead of ILogger etc, I pass an Action to the constructor, which is then called when this "handle" leaves the scope. Basically using C# language constructs to mimic C++ RAII.
This looks like RAII from C++/Rust. Very nice and clean way of doing things at the end of the scope.
Well I do not know about C++ but this C# "using" has existed even before Rust was invented AFAIK.
@@DhanarAdiDewandaru it's been used in C++ even since before C# existed en.wikipedia.org/wiki/Resource_acquisition_is_initialization
I’ve also used this when setting and resetting the Windows cursor in WinForms back in the day, must have been using this for 20 years now, given that I started using C# in 2001. Pun intended. 😀
Same, only in WPF rather than Winforms
I would use readonly structs (with IDisposable) for those situations
Or a readonly ref struct, but that cannot be used with await.
ref struct can't have interfaces so you need to use duck-typing. It'll just make it more confusing. Readonly struct is what I do.
I wish there was a channel like yours for the Java community :/
interesting usage for using! btw, I like the 6_9 eyes...
Don’t interpolate messages for loggers. they are usually cached, interpolation would degrade performance, and remove ability to properly sort messages. Just add ms to args array.
Not in the default logger but what you are saying is valid if you are using Serilog
@@nickchapsas you never now how logger utilized, the default logger can use serilog inside, or it could put messages into msmq where message template would matter. Anyway "it depends", and string interpolation would work correctly only in some situations and in another it would bring problems.
Very clever trick!!
Hi Nick! Thanks for the video. It is very helpful.
Since you are a Rider user, Could you please provide a video for us and explain the cool features of Rider + Extensions or configuration etc that you use?
Thanks!
Have seen this and similar patterns for gathering ephemeral metrics in the wild. It’s good… but… beware nesting! Actions in the dispose can start to impact other metrics being gathered and give you false readings,
Very nice as always
Χαιρετίσματα από έναν παλιό του MXC :D
As always use a struct for anything that doesn't need to be moved to the heap. In this case you could even get away with a readonly ref struct and implicit dispose pattern implementation...
A readonly ref struct cannot be used with await.
Just use a normal struct and implement IDisposable.
There will be no boxing either.
amazing!
Could you create an example about best way for writing file in multi task and large file >= 70mb?
Thank you Nick for clean code
Very interesting for you to bring this up. There's certainly been times I've though up similar creative uses for using / Dispose(), but always steered away because it felt like a code smell (or at the very least, a hack) since it breaks the assumption of cleaning up unmanaged resources.
As long as you do it in library code, you are fine. Library code has internal code anyway so you aren’t hiding something that shouldn’t already be hidden
What a nice concept, thanks for the vid!
Using that to do push/pop debug group in OpenGL for nested profiler markers
Hey great video! Btw what is the tool you use to highlight with red color box and draw etc?
Nice. Should probably add something like 'if(_logger.IsEnabled(_logLevel))', since the '_message' can be formatted with the '_args'?
Yeah he whole IsEnabled thing is actually a topic I have for a video coming in mid January, but yeah you are right
What’s the deal with the eyes overlay at the 9:22 mark?
I wrote exactly that in my profiler library two years ago and it worked beutifully - but often i need that in a specific scope, so i prefer to use the old way -> using (...) {} instead. But nowadays we dont use that anymore and fully removed all the profiling code. Profiling is only done with BenchmarkDotNet now - which is fine, but i would have prefered to leave the profiling code in, so i can manual profile specific codes if needed and get metrics such as CPU-Cycles - which you wont get with benchmark-dot-net.
Very clever.
Is there any nice way to avoid writing try catch block on every method and still catch the exception message(Local exception message not the global exception message)?
This is great
Very clever and clean.
Love it!
9:01 this is where the fun starts 😋
Awesome video.
Brilliant video
Yeah but this kind of profiler has a scope. Imagine if you want to measure your first call "var weather = await ..." and then use var weather after the measuring ends. In such case you have to declare var weather before "using" statement. And depending on part of code you want to profile it can be many more vars which you have to declare previously. So the code still may be pretty clunky.
Wouldn't the compiler (maybe in the future) be able to determine WHEN something goes out of scope and dispose "too early"? Your discard variable, technically, goes out of scope immediately since it's not used anywhere else. I thought about this earlier and think that's why I prefer the "old" using syntax where the scope is very explicit using the curly brackets (accolades). I think this is prone to optimizations done by the (future?) compiler.
If they did that they would break 100% of existing code so no they won’t do that. Using’s purpose is clear. Replace try finally and a dispose call, with using. Any change to that breaks everything
@@nickchapsasI've tried to demonstrate it here: dotnetfiddle id JQiIFd
Using the new syntax the compiler MAY be able to determine (in the future?) when an object goes out of scope. Correct me if I'm wrong. The documentation on the using statement says:
"The newer using statement syntax translates to similar code. The try block opens where the variable is declared. The finally block is added at the close of the enclosing block, typically at the end of a method."
Note the "*typically* at the end of a method". No guarantees that I can see?
@@cassiel6666 Hmm, "The finally block is added at the close of the enclosing block" makes sense and, indeed, sounds more like a guarantee. Bit of a shame though, I can see situations where disposing earlier could be beneficial. Then again, using the "old" syntax should solve that. Cool. Thanks for pointing it out!
9:22 Nice.
Very interesting! I don't think I would use it for production tho. Other programmers would hate me 🤣
It’s used every single day by… stack overflow 😜
At 7:40, you mention a website, i don't see it in the description of the video... What is the website?
sharplab.io
I still don't get why should i use a delegate and an interface. Any advices ?
Cheeky 9:22
one possible improvement though; a better place for this overlay would have been 4:20 don't you think? :)
3:45 what's the reason of placing a string like that instead of just using string interpolation?
Because of structured logging
@@nickchapsas would you mind telling me the technical name for that kind of string presentation? I'd like to investigate it.
@@_CazaBobos The technical name is "structured logging". Here is an in-depth explanation: ruclips.net/video/6zoMd_FwSwQ/видео.html
I love it in theory, but I'm worried that I'd lose the less senior devs working with me.
Your courses are listed on udemy?
No, only on Dometrain.com
Thanks for the info.
P.S.: fix the description of the video :)
What’s wrong with the description?
@@nickchapsas injeciton --> injection
@@evanboltsis Nice one thanks
clever, very clever!
This is similar to python concept of "context manager"
Video starts at 09:10 if you already know what the using statement is
using string interpolation with logger is a warning in .net 6.
What do you think about that ?
Because it lead to memory problems. Strings are immutable so any string combination will be allocated as a new string. That was always a problem and especially with serilog it would also hide any property captures
I don't know how I feel about using IDisposable like this.
what is the try{} finally{} even doing why not just call the Dispose method anywhere
awesome.
At 10:40 you chose to use a field _args as type 'object?[]' but aren't they nullable by default?
Not in my project where I have null reference types enabled
Ayo why does your top bar in rider look different to mine? Some kind of plugin?
I’m probably on the latest version
@@nickchapsas I'm using the eap version (edit: 2021.3.1 is now released, so no longer eap). And the 2020 version. Both of them don't look like this. Maybe it's a setting that you have?
@@barmetler View -> Appearance -> then, select Toolbar instead of Toolbar Classic. Always take about 10 minutes to read “what’s new” every time new version released. 😂
@@restveggie6155 Ah I was looking through the settings, that's why I was confused. And also, I don't have rider for that long, so I haven't gotten to see every "what's new"!
Also, I'm supposed to read? That sounds like work, ngl XD
But thanks, it looks really good!
Unless you use a using scope { ... }, you may end up timing stuff you do not want to time :-)
I think this a bad example of using.
Using should be only be used for release resources and there are better way for logging.
Generic intercepters, decorators or logger with a method like " Task Log(Task actionToLog)"
I explained in the video that even though what toured suggesting looks reasonable, it will almost certainly have closure issues.
Nice! :P
How you are navigating to source code?
I am using JetBrains Rider as my IDE and it supports that feature
JetBrain resharper vs extension could do it also if you need to.
Nick: don't even try
Feels hacky. Hidden behavior. Clever tho!
Dmn smart indeed... :p
Name it "From Zero to Hero: Dependency Injection in .NET CORE with C#"
It's not .NET Core. It's .NET
Sorry, I might have gotten confused. From introduction, in seemed like the course excludes .NET framework, includes only .NET Core 1.0 to 1.6.
This feels so dirty :D
That's on-targeted use of 'IDisposable', you don't release any resources thus you violate single responsibily principle.
This is so funny, while I‘m watching this Video I‘m currently implementing exactly this…
👍🏽
At ruclips.net/video/iqt7bqAm27U/видео.html, can't you just make it even shorter by discarding the return var and simply write
using (logger.TimedOperation(nameof(GetCurrentWeather)))
{
...
}
?
Lol @ the 69 joke 😂
Perfect but I do not like that, hiding behaviour
It is ok to hide behaviour in library code. This shouldn’t be used in main project code. However most of the libraries you are using, do this and you just don’t know about it
Another word for hiding behavior is "encapsulation" ;-)
@@PlerbyMcFlerb I suggest you to learn more about encapsulation
When you want to call a method of a class (instance), it should be clear without any side effects.
Here, when you call that method with ugly using, you should know it stops the watch at the end of the block. Also, this approach depends on .Net C# version
I see it like goto. You can apply "goto" every where but should apply?
The purpose of using "Dispose" and "using" is clear, disposing resources automatically when exiting that block.
Because you can do, it does not mean it is perfect and the best choice!
I prefer other approaches, this approach is not related to encapsulation at all
As Nick mentioned, it is OK for tools, frameworks and libs with documentation
One approach
private void Main(){
_logger.TimedOperation("message",
()=>
{
//...
} );
}
public static void TimedOperation(this ILogger logger,string message,Action action)
{
StopWatch sw=new StopWatch();
sw.Start();
action.Invoke();
sw.Stop();
// log
}
Just because you can, it doesn’t mean you should.
Dispose should release resources. Nothing more.
Why? Btw this is exactly how Serilog, the biggest logging framework, is doing timed operations
@@allannielsen4752
Your code should show your intention.
Dispose shows your intention to release some locked resource. You are "lying" to the reader of the code or atleast your code don`t present your intention.
Is like making your confirm button in red. Mixed message are bad for the understanding.
This is very similar to RAII approach in C++ and Rust that uses destructors/drop to do actions on scope end, not just resource management, despite the name. It was proven there that this approach is perfectly valid for things like this
C++ and C# are not the same. What is idiomatic in one may be surprising and dubious in another. I've spent more years than I care to think about cleaning up C# codebases that was butchered by people who knew C++ but never bothered learning the difference when they changed language.
As just one example, a destructor in C++ always happens as soon as the object goes out of scope, so you just need to create the object and you know it will work. RAII FTW!
The Dispose method in C# is not a destructor. It must be called (perhaps explicitly, or perhaps implicitly by using). Forget to add the using statement when you create the object shown in the video? Dispose won't be called. But what if the class has a finalizer? Dispose might be called... at some point... in some future garbage collection, which could be literally hours after the object went out of scope, in a completely different part of the code, on a whole different thread.
Does that sound like the kind of bug you want to investigate? Not me. And there are other problems with it too.
Exactly as the other commenters said: using/Dispose in C# has a very specific meaning that C# programmers know and expect. Breaking that meaning is dangerous. Please don't do it, unless you have a *very* good reason, and then add appropriate tests plus comments explaining why you're breaking the rules (which you obviously won't do for a technique that's supposed to cut down on cruft, so won't happen).
The complaint should be that IDisposable is not an alias for something like "IPromptFinally" (don't search it, I just made it up), which we would want to use in this case. Use of a feature in a way not intended is definitely a whiffy hack, but this mostly smells because IDisposable is overly specific in it's implied use. It works fine as a more general IPromptFinally, with the specification being exactly what you would expect from such an interface.
I do completely agree that "it's a minor hack that C# programmers get used to" is not a healthy thing to have in the language, but blame the language devs who put in a general feature with a specific name, rather than the user devs who have realised it is perfect for their use case despite the misleading name. We are not talking about undefined behaviour, or behaviour that can change without braking the expected use case. It does exactly what we want and always will.
If it really bugs you you could actually alias it to a more appropriate name for the use case. The fact that it is just IDisposable under the hood shouldn't matter as much.
69?
sexy!
69 lol
Hiding logic inside dispose it's wierd at least
You can do it in library code. It isn’t recommended to do it in main projects
Mh, so you are basically lying to the next guy by misimplementing IDisposable so you can save some indentation. Smart indeed.
If you've been using pretty much any popular .NET library then you've been using it too without even knowing
Hey guys, I would say this is not a good practice for such a scenario, here are the alternatives:
1.use AOP if you want to log the time consuming for the api with correlation id
2. if you want to control the scope, please try to use high order funtion to encapsulate the logic in this presentation, then warp your logic into lambda expression and pass into it
Smart... Nice!