This doesn't make any sense 0.2239ns is essentially the time it takes to execute a single clock cycle on a 4Ghz processor. That's hardly enough time to even execute the return instruction. Aside from the suspicious benchmark numbers, all this optimization boils down to is removing the indirect call of looking up a virtual function address in the classes vtable and replacing it with a direct call instruction. At that point just make the method static / create a free function and you get the same effect.
@@anderskehlet4196 "performance" will be relative and YMMV. If you have a function that does some heavy computation, the overhead of a call vs indirect call won't matter at all (we're talking about a ns or two savings...). It makes no sense to just blindly seal all your classes for "performance". Take any real class that does something useful and seal it and measure how you get no real world performance benefit from it, this whole video is just a contrived example with a poor benchmarking setup.
@davidparker5530 That's a bit short sighted in my opinion, obviously in the grand scheme of things one shouldn't chase tiny improvements, but the relative difference is key here, if you implement this in all hotpaths in the application the change is rather dramatic and you won't even need a benchmarker to see itz you can see it with your own wet eyes. There are lots of places where C# is applied and Is performance sensitive. Try what happens when you're iterating stays in the teens of thousands with sealed vs open, it's huge for very tiny effort. Just write one more word!
Praise from a random viewer: I’m very much a reader, and if I expect an article and land on a video, I’ve already hit the back button. Your videos are not only excellent enough to transcend my prejudices, but to provide some income with perks as well. Many thanks from an internet stranger for your fantastic work.
Thank you for this video. I enjoy videos about the performance and features of C#. By the way, I wanted to mention that in .NET 8 (I tested it on .NET 8.0.101), there are no significant changes in performance when running methods and changing arrays to spans. However, other cases still show the same performance differences. This means that it’s still important to seal your classes in .NET 8.
I hate this advice because I have managed to fix so many broken framework and library classes over the years by using inheritance. And entire avenue of bug fixing possibilities are quickly disappearing. These classes are not intended to be inherited and extended and these fixes are brittle but at least they're possible. I'd rather be able to work around an issue today and fix my workaround tomorrow than just be stuck.
I tend to say: a good quality of a library developer is to be aware that you might not think of what is needed NOW, but end users might come up with different ideas. I like this advice for non-library projects. It's like free gains, and you can always change it in your project. But a good library author will allow users to extend it however they see fit.
There should be a setting which instructs the compiler to make all classes in a project sealed if they are not inherited. Could be usefull when you don't plan to roll out your code as a reusable library, which is most often the case.
Probably easier to write a simple post-processing tool in something like Mono.Cecil to do that. You have to consider that in C# it is possible to load DLLs dynamically. Something might get extended in a different compilation unit, the existing tools haven't been designed for this case. But it's maybe a 100 lines in Mono.Cecil to make such a change.
@@julkiewitz Yup. Late binding very likely goes right to the heart of why there is a performance benefit to doing this. Apparently someone else has tested this and says virtual is the kicker, sealed/unsealed otherwise won't matter.
I solved this issue a long time ago by changing the class template in ReSharper/Rider to internal sealed. My goal was not to improve performance, but to not accidently exposing a API I don't indent to expose. It's nice to see that the performance is also benefiting from it.
If the C# design team believe it's a historic mistake that they cannot fix by changing the default behaviour then my first thought was why don't they just change the class template to stop it being as big a problem going forward?
Totally agree. Having been a Kotlin developer for the past few years, I've learned to appreciate sealed (or final) by default, and now I apply it as a matter of habit to C# projects. Whilst .NET doesn't implement sealed by default for classes, it does for structs, which is why you can't extend them. If you look at the IL for a struct, you'll notice they're marked sealed, and as .NET has no mechanism to 'unseal' a type, you simply can't extend them. The other thing that would be nice is if C# had a language-level equivalent of Kotlin's 'sealed' (which is not the same as C#'s sealed), where you can create an abstraction that is only intended to be implemented within the same assembly. You can achieve this however by using internal constructors, so that it's not possible for extension beyond the current assembly.
As a frequent C programmer, one of the things I appreciate about C# is the ability to throw an OOP concept or trope like garbage collection overboard. There's definitely a place for them, but if I'm doing something like DSP it can be a performance factor of over a thousand just to clean up after myself instead (unchecked, unsafe), and work with buffers directly. So, I appreciate the occasional "do we always need this" introspection from the C# team.
As a kotlin dev, I really like the sealed nature of language, But coming from Java/C# background, I used to have a mindset that classes should allow for inheritance. I used to think that just encapsulation means a lot of objects running around in memory. It took me a while, a good mentor, and a lot of performance benchmarks to realize the runtime benefits far exceeds the memory overhead, and also that a badly designed inheritance will actually consume more memory than encapsulating multiple objects. Great video as always. I really love all the benchmarks numbers that you show to validate your tips.
Badly designed inheritance has a ton of disadvantages, strong coupling, hard to understand, hard to maintain. Sometimes impossible to refactor, because 20 different inherited classes are strongly coupled, and minor changes, can lead to subtle (but devastating) behaviour changes. In 90% of the cases: Prefer composition over inheritance
@@fr3ddyfr3sh Absolutely agree that you should prefer composition over inheritance in 90% of cases. The only problem with this mantra is that most people don't understand the other 10% of cases.
You'd think the runtime could handle this. The jitter knows if any classes inherit from this one, and if additional assemblies are loaded at runtime then the affected code could be re-jitted.
It probably wouldn't be a good idea to run this at JIT due to the performance impact of that analysis. Much better to evaluate at compile-time and seal all classes without members that inherit them.
Yeah! Since I played along with Kotlin some years ago, I got used to sealing all my classes in c#. It is not only a question of performance but, first of all, good an robust design of my code.
In a comment I made on one of your previous videos, I mentioned the preferred "composition over inheritance" pattern and got so much pushback. Glad to see you mentioning this as well!
Like every advice from every "guru", sealed classes in every language are usable in small number of use-cases. If you can't inherit a class then every framework that is depending on proxy design pattern (for creating "sugar" classes) will fail and the list of frameworks is very large RestControllers, ORMs, DI etc. And why those framework will fail ? Because all of them depend on proxy design pattern, which can be implemented in three ways 1. as a pair (interface and class) or 2. as inheritance of the class annotated with metainformation. 3. provide factory methods for every sealed class, that will be use by the frameworks (mostly use in DI) If you are using sealed then you must 1 or 3 This means every seal class needs to have "dummy" interface in order for the framework A to create "the sugar" sealed class or factory method Congratulations, instead of one "sugar" class A, know you have sealed one "sugar" class A + dummy interface IA / factory method and your code base is now twice as big. Don't get me started on ORMs, there is a high change that the amount of SQL generation will be double or you will wonder why ORM always generates insert statements. But this is just the software developer perspective From library POV - in order to fight sealed word, the libraries have to provide compiler hooks and now instead of Microsoft or your fav language company you have to wait 3-rd party company to fix your compiler bugs Classes shouldn't be sealed by default, because they are almost always useless
@@nickchapsas yes However, if project with unsealed classes has 1000 classes then project with sealed classes will be at least 2000 classes And that is the price to pay for using sealed keyword by default
I jumped in hoping for a great performance boost after sealing ~1800 classes in my project. Perf increased by 0.3% overall. So kind of a mixed bag... Ah well, thanks for covering the fundamentals
I'm curious if this has any effect after the code is converted using IL2CPP as is often the case when making final builds on Unity. Really interesting and awesome never the less!
Basically no difference in 2021 LTS as the .Net versions supported are Standard 2.1 and .Net 4.0. Neither of those have the specific optimization being talked here in the IL (using direct call instead of callvirt).
6:10 it's easy to fall into a pit of misconception, so I'll drop my thoughts there Notice those ZeroMeasurement warnings down there, those say that method duration is too small to be taken into account, often times because the method is inlined and its execution takes virtually the same time as calling an empty method, meaning it's "the fastest you can get" when comparing vs calling a method. So it's not actually "38x" faster or "1000x" faster, it's "the fastest you can achieve".
This is probably an effect of the methods not being correctly aligned for an ideal implicit inlining scenario, but either way if you do notice a zero measurement warning, you should assume that it's happening sometimes. Remember that given how inlining isn't perfect, were that same zero measurement warning to appear every single time, the performance could easily not be as high in as many as half the cases in a real world scenario. This is microbenchmarking and sets up and ideal scenario to juice out the maximum performance, isolating as many external variables as possible. TL;DR always worth noting that *even once* you get a zero measurement warning.
@@AlFasGD obviously you're not wrong, and the information you've posted is super interesting to me so thank you! But I think the benchmarks here are just to show clearly that there IS a performance gain for zero cost, the video is explaining why sealed should be default and demonstrating that there is ANY performance improvement is a good enough argument to support that.
You noticed in this video that inheritance now is considered an antipattern. I tried to find some nice examples of how we can replace inheritance with composition, but I couldn't find some nice examples. Could you please make a video on how we can refactor code with inheritance to the composition?)
Hey Nick I'd like to say thanks for your video and I have a question. The performance sag in your example could be a consequence of the virtual method table (not just because of "sealed" class). If you try to remove the "virtual" word you will see the other results. It does not negate the fact that type casting and some other operations are faster with sealed classes, but I think the "sealed" is not something that always makes performance better.
Actually was wondering about that and tested it ... if it's not virtual its just the same speed ... so sealed/unsealed has no impact on none virtual methods.
Sealed can be very annoying too. Like when you want to modify third parties code. Extend it and modify one function you want. With composition you cannot input it into functions that take the original class, only that take an interface, so you have to copy all the functions too that take or depend on original class.
That's a feature, not a bug. You're going to be doing that so so much more rarely than actually working on a normal class. Allowing instance means that normal code now needs to maintain invariants even in the face of fairly hard to predict code replacement. Your subclass needs to be tightly coupled with the base class, and needs to understand what the actual class internal api is, because if the base class ever changes anything, you're likely to encounter all those hidden assumption on a bug report. The effort of rarely copying a few method declarations is so much smaller than the effort of dealing with unnecessarily flexible code, especially if anyone was ever so unwise as to abuse that flexibility. Inheritance as a form of method interception is a recipe for unmaintainable spaghetti code.
This may be great in theory, but in practice when trying to test either legacy systems or library without a good testing strategy. Sealed classes have stopped me from writing unit test many times. Especially when you want to implement tests without changing legacy code. For new systems it might be more applicable, but they will be legacy system one day. So how do you want to leave the code for future developers.
This is a design flaw in your code. Third party services/libraries should be abstracted using an adaptor that conforms to an interface that you've written. You can then trivially mock the adaptors in your unit tests. There's no need for those third party libraries to be unsealed if you are properly abstracting your dependencies. You should also avoid injecting concrete types as dependencies whenever you can.
@@gnack420 Well that is also a question about theory and practice. Yes I know all about abstracting dependencies. I have 10 years experience with looking at old code that either only is the layers of abstractions or looking at code that has no abstractions. And the one thing that made me utter profanities more then others is sealed classes. When trying to test. And it’s a lot easier to end to end test code without infrastructure when third party libraries do not use sealed on their classes. Yes I can write a lot of unit tests with my abstractions but if I send the wrong property in the wrong argument in my implementation. The only thing the fast code will produce is more problems for me to clean up in production if QA don’t catch it.
This is correct Magnus. Unit testing is one of the shortcomings of sealed classes. And being told I have to extend my object model to support unit testing because I sealed all my classes, flies in the face of everything we learned about object data models, and the quest for simplicity and extensibility. I admit there may be outlier use cases in which sealing all my leaf classes is a necessity or a sound design choice. But for the vast majority of object models in the world, even considering C# little semantics flaws and idiosyncracies, sealing a class should be a structural decision about the object model. Not the result of an arbitrary generalization to gain infinitesimal performance gains on an application that has no performance issues in the first place, or where those performance issues are understood to live somewhere else in the code. This wasn't one of Nick's best days.
Three examples in which I would want to seal my class: (1) I'm writing a class with value-type semantics. (2) I'm writing a code generator for an engine or interpreter which relies on specific types being consumed. (3) I'm writing a utility math class composed entirely of truisms. (On this special case, extending behavior is best achieved through composition). All in all, Microsoft advice in the sealed keyword documentation page still stands. In short, do it only if it makes sense. Avoid it otherwise.
@@MagnusBertilsson I don't understand. When unit testing a class, you're supposed to test the methods of that class, no? Ergo, instantiate an object of the class, call its various methods with various parameter values, analyze the results. Sealed or no sealed class makes no difference, legacy or brand new class makes no difference.
I think if ide(s) like Rider and Visual Studio add this feature (Adding sealed keyword in snippet code of Creates a class declaration) It will be reasonable and more practical.
The performance improvement on the methods suggests that they are being inlined, and the performance improvements on the is / as would be because it can directly check the type instead of checking if the type being tested is assignable from the type of the value.
This is not a "design flaw". This is a historical artifact of object oriented languages where classes are designed to be inherited by default. This also permit classes in libraries to be extended. Clearly over the years there's been a move away from object orientation with better methodologies of extending features. C# was based on Java and other OO languages at the time.
@@matthewwood4756 You can accomplish pretty much anything that is achievable with inheritance with composition instead, and it is usually more flexible, cleaner, and separates concerns better without sacrificing encapsulation.
@@timseguine2 - I’m not necessarily disagreeing with you, but using true OO principles of multiple inheritance, the same can easily be achieved that more readily/accurately “models” real-world problems/designs/solutions. “Composition” is the answer/pattern that has naturally evolved due the mainstream implementation/acceptance of single-inheritance languages/compilers.
@@matthewwood4756 No. Classical OO often confuses the object hierarchies' isA relationship with the isA relationship of the thing they are modelling. These aren't in general the same relationship from a mathematical standpoint, so it doesn't make sense to model them like they are the same thing. The argument against inheritance comes historically from the Liskov Subsitution Principle and has absolutely nothing to do with single versus mutiple inheritance languages. Or put more charitably, you are reversing cause and effect Yes in languages with single inheritance there is additional motivation to favor composition over inheritance, but that was partially the motivation for them deciding to force single inheritance in the first place.
I usually always avoid implementation inheritance. So I have been doing this for a while. And even the general rule is: either design for implementation inheritance or forbid it. I usually design for interface inheritance, so yes, of course I seal my classes since I am not designing them for implementation inheritance.
Hey Nick, thanks for the great video. I have two questions. 1. Is the performance difference only in .net 7, or is it in older veresions as well (.net 6, 5, .net core, .net framework)? 2. Usually there's no such thing as free lunch. In the video you displayed making class sealed (if possible) as an absolute positive, with no negatives. Are there some known drawbacks to doing it? Thanks :)
1, Yes i applied to previous versions as well. 2. The "negative" is that you can't inherit from it anymore but if you were never planning to do so then it's just positive.
@@nickchapsas Thanks for the quick reply. Most of the classes are not inherited anyway, so this might give us some performance gains. I'll give it a shot.
@@nickchapsas Could you please share your opinion about how to test such sealed classes? could it be measured as "negative" point of this approach? Because I can see a problem with some legacy or some projects with non-structured test strategy where such "sealed by default" might lead to issues when we try to mock them or create a stub for such classes (if there was no adaptors which would allow to mock them instead of the classes directly). Thanks!
We really need a way to specify different c# 'versions' so features can be changed retroactively without having to worry about backwards compatibility. Any new project will use the most up to date recommended version and if an older project is upgraded, they can change individual settings to reenable compatibility modes. I think that would be awesome.
@@MiningForPies no this is not what i mean here at all. i mean: instead of classes being implicitly open by default, and you have to seal them with the sealed keyword, in the recommended version classes would be sealed by default and there would not be a sealed keyword, but rather an 'open' keyword. the same applies to other features, for example _ in lambda is a discard, even in single argument lambdas (which is not currently the case because of backwards compatibility concerns by the .net team) and there's a whole list more of similar features.
there are already programming languages that have a 'version' model like this. nick has even mentioned it in a video about it a few months back. (though i cannot quite remember which one)
Wtf are these replies people don't read 😅 I commented exactly this and saw this comment. Do you know what the stance of the c# team on this is? It should have been discussed at least right?
So I had a look as to WHY, and it's mainly because a call to a VLT can be being turned into a direct call by the compiler, which makes sense (still not idea why the 'is' and 'as' operators run faster though) [edit]Same idea, the runtime doesn't need to walk the inheritance hierarchy.[/edit] I suspect that this will have less of an impact if you have a deep inheritance tree with all of the leaf classes marked as sealed, because any method that at runtime can accept the base class will still have to do a lookup. That said, if people stick to composition > inheritance that shouldn't be an issue ;-)
Does compiler really can turn overriden and hidden functions with direct calls with ease? Or more advanced inheritance based trees are still at tables? I know that sealed in real life barely makes performace impact.
Interesting....One case you need to be careful is if you have created a nuget package / dll and you seal a class that would be very useful for your user to inherit from. This has happened to me and it caused me alot of pain.
Rider has an inspection severity setting for this outside of .NET 7 under Settings / Inspecting Settings / Inspection Severity / C# / Class can be made sealed (non-inheritable)
I've been making my classes sealed by default for years now. I don't do it for performance reasons (though every little bit helps!), I do it because I don't like to use inheritance when I don't have to. "Prefer delegation over inheritance", etc. I only wish VS made it easier to change the default class template, so I could make those changes more quickly and easily.
I think right now it's needed to be included into linters warnings by default like "The class %ClassName% is not inherited anywhere and can be made sealed". Is there any suggestion for this on JetBrains YouTrack?
The Microsoft Documentation from April 2022 says: DO NOT seal classes without having a good reason to do so. "Sealing a class because you cannot think of an extensibility scenario is not a good reason. Framework users like to inherit from classes for various non-obvious reasons, like adding convenience members. See Unsealed Classes for examples of non-obvious reasons users want to inherit from a type. ..." Also Mocking is a Problem.... thoughts?
I clearly disagree with that as well as many developers in the C# and .NET team. You can clearly see that MS themselves don't follow that rule. Docs advice is designed to be beginner friendly. There are tons of advice in there I disagree with.
You do have an option to 'extend' sealed classes. You can do it with extension methods! The same article you are referring to though also ends with: "Portions (C) 2005, 2009 Microsoft Corporation. All rights reserved." and comes out of a book "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition", Published Oct 22, 2008. We are back to that since 2000 to 2010.... the developer landscape has changed significantly and this is just one of many 'viewpoints' that have changed/shifted. There is definitely a big issues too with not sealing your types. Because it implicitly means that you as a developer, especially if you write a library, would have to put a lot of defensive code in to ensure that outside users implement the types different or wrong. Personally I also favour using extension methods even on types that are inheritable if possible of course. If I just need to add a helper, like addind an AddRange() or some display or factory method or something. Rather then to deal with the inheritance and having to implement then everything the thing needs to have. I always have to think back to one time I had to do it, having to add a helper to a certain type. I had to do it by inheritance but due to other bad design practices by the api designer, I was forced to implement 30 other methods.
@@paulkoopmans4620 Extension methods currently have a ton of limitations though. For example, you cannot do something as basic as adding a property to a class using extension methods. Yes perhaps this is eventually coming but it’s probably at least 2 years out from being included in the language.
@@bass-tones Yes I understand that extension methods are only able to do anything with the object by using its public interface. The name 'extension method' is a bit deceiving and I realize I am actually not 'extending'. The class does not change by creating extension methods. A sealed class though, which this topic is all about, will never be able to get any extension whatsoever, but at least you can add some helpers like maybe an AddRange() method that calls the single Add() method provided by the library. Or maybe a Search() or ToEnumerable(). This is also still where encapsulation might also help you out, IF and ONLY IF the designer of a library actually has done it properly and depends on interfaces. If you ever wrote an external facing api (not webapi, but as a library I mean) then you know why you would try to make pretty much everything internal and the stuff that is public you very likely want to seal. The actual classes, from a library perspective, that NEED TO BE extendible by inheritance as the only option in order to be useful to the user, is probably near zero. Sealing your classes and guarantee yourself that your classes do 'exactly' what you expect them to do far outweighs the pain, risk and cumbersomeness that you would have to add if you leave your classes open for inheritance and your code consuming then would have to have all kinds of defensive code and checks in them.
Analyzers depend on sdk version. You can use the new sdk to target older versions just fine. Worst case you could multitarget, e.g. for nullability, but that won't be necessary here.
I was a little skeptical. Inheritance is a big feature to make a general statement about throwing it away. But interfaces exist, so having most classes sealed and interfaces available could give the performance boost of sealed while allowing for people to make their own API compatible classes. The main concern is that if something is missed and there isn't an interface or some other way to customize that part of the API and the relevant class is sealed then some dev somewhere is gonna break out reflection and do some sketchy hacks to get around it.
The performance benefits also seem to apply (not fully though) for an "open" class that uses sealed override methods. (public sealed override ExampleVoidMethod() { })
I feel like many things, like Sealed being the default, could be changed, similar to NRT(Nullable Reference Types) - by adding a setting in the .csproj file. true true // This is currently not even a feature, but it hopefully will come soon.. and then potentially add a wrapper tag: 2022 // This would then set all the default values (like NRT to enabled, both of the preceding values to true, and so on..) C# is an aging language and has a lot of flawed design decisions. I feel like, at some point, this needs some way of being addressed. Why do we, in 2022, have to live with poor design decisions made in early 2000s when we don't run _any_ code from that era?
you personally might not. But I know for a fact that the companies I have worked in since 2005 all are still alive. There is a fair chance that some of the original code is still there and running.
@@paulkoopmans4620 They could still easily interoperate - It would still compile to the same lowered code. It is however a definitive problem with the consistency of the code across multiple projects, but I'd argue that's also an issue with NRT
@@modernkennnern I was just commenting on your claim that in 2022 there is no code from that era running anymore. :) Again given that the companies that I wrote .net 1.0 code for, in the 2000's are still around, I do want to believe there is still old code bases around. Now this particular 'flaw' of not sealing by default is even prevalent for code created last month. And I agree is a problem and that it is hard to break the cycle. I see what you are trying to say with comparing it to NRT. Yes; they are solved the same way. But also both of these suffer from the same problem. If you have some old library code sitting somewhere in your massive code base and you never get to address it, it still needs to successfully compile today. Therefore the compiler has to generate instructions that give the same behaviour today as it did 20 years ago. Which is why both NRT and this one will just generate warnings but will compile regardless. The reason why such a flag in a project file could not work is because for some new parts you finally want to start with classes that are sealed by default but this dark grey area of your massive project should not generate a sealed class, and unfortunately both of the lines are identical and the compiler could not tell which one to generate as which type. I appreciate that for a net new project and/or solution it could. But the having the warning and your IDE treating it as such is frankly the same experience.
Great video, as usual. One point I noticed in your tutorial (and Stevens issue) is that both the sealed class and the open class are themselves inheriting from a base class. Would the same hold true if both of these classes were not explicitly inheriting another class?
5:50 this is nonsense, I'm not sure if you talk about this in other videos. The fastest measureable time in theory is one CPU cycle. For a modern 5GHz machine that would be 0.2ns (you, too, have a 4.9 GHz processor). If the benchmark returns a value smaller than this, there's a measuring problem and indeed, dotnet benchmark also warns you about this right below the table, in red: "This method duration is indistinguishable from the empty method duration" and further more, the median is 0ns, which means, half of the benchmarked values were 0ns. So "1000times faster" is nonsense. To measure super fast code you can not purely rely on the benchmark framework. It's a bit tricky. You might need multiple evaluations in the benchmarking function. The benchmark values always contain a bit of noise. The OpenVoid method takes 1 CPU cycle and the OpenInt method takes 4 CPU cycles. So you cannot be faster than openvoid because you cannot be faster than 1 CPU cycle.
But even the "warning" message proofs, the 'sealed' versions are faster. The warning is not produced for all versions. So it's significantly faster, this was what had to be proofed. And it says "It's close to a method doing nothing", which is correct, cause a method just returning a constant integer, is the same, as a non-virtual method returning nothing. You are correct, it's not exact you cannot say it's 1000 times faster, but you can say "it's faster" and probably at least 10 times faster.
Seems like the performance gain could be a compiler optimization without having to seal classes manually. If there are no extensions of the class, the compiler should be able to know that.
Not when the classes are public. Anyway, I expect some edge cases could become problematic with such optimizations, so it sounds better to expect programmers to make that explicit in their code.
@@davidparker5530 I didn't test it, but I'm thinking of reflection, for example, but I'm sure there must be other cases too. I can't post links without angering TH-cam, but there's a "How to dynamically create a class?" thread on Stack Overflow that has code samples.
Can't they add open keyword and then have a optional property in project file to say classes are open or not by default? something like nullable or implicit usings. this way it's backwards compatible, and if you make new project template classes to be sealed by default it gets adopted more easily and just by that option being there it makes c# users search and know what the benefits are.
how? Maybe I am not seeing what you mean? the point would be that some old code from the 2000's like: public class X {} , stays a public and open class as you cannot infer whether or not it was intended to be sealed. This is the whole reason why you would have to 'close' it by adding it in the first place. They need to keep the compiler, runtime and JIT separate from IDE. I have said this in other videos too why adding a new keyword is not as simple as it sounds. Writing a lexer and parser is one thing. And to change it' to support a new keyword is ok, updating documentation also. But what if anybody out there has a class called open? or a variable? This is the reason the team has been favouring re-using existing keywords (like this in an extension method). They are guaranteed that nobody is using a keyword for something. And even if somebody did... they are doing with an @this. With the warning and the ability for you to treat it as a warning or error is the best they can do.
@@gnack420 Of course it doesn't matter for already compiled libraries from nuget, or any source for that matter. I did not mention library in this response at all. The obvious problem is if you want/need to recompile old code. Btw; library obviously means two things; there is libraries (which we should call packages) that you can use from sources like nuget or proprietary downloads from a provider, but also every one of your 'project' in your 'solution' compiles to a class library as well. And I get the sense that a lot of people seem to be fortunate and only work on relatively new stuff, and that's great for them, but from experience I know that other people do have this battle in their day to day jobs; having some lingering code in the project that never gets touched and is over a decade old. And for this particular issue it could actually affect code created a couple of moths ago to be fair. it is these cases where the .NET team is making conscience choice to keep on compiling 'public class MyClass { ... }' to an 'open' or 'unsealed' class because they simply cannot guarantee it would not be inherited somewhere. So they are left with basically keeping it the way it is.... open by default and close it when you want it... because of backwards compatibility. And re-using the paradigm that they have been for a while... having the compiler spit out warnings that proper IDE's like Visual Studio, Rider, etc. can catch and help the developer take actions. Just imagine if you would work in a company with tons of legacy code and all of a sudden your build pipelines would fail building your code and you were now tasked with 'fixing' or 'patching' that, just because the compiler is now requiring you to add 'open'? Or you wrote a package last year that you are selling and you have 5000 customers using it. Your package is designed around a concept where your customers are allowed to inherit from a handful of classes. You run it through an updated compiler, which then closes all these classes that you actually intended to be open? Of course you would fix it... but the point is that the behaviour change of the compiler forced you into changing your code. Amplify this issue and that change might affect thousand's or tens of thousand's.
I wonder if there is a similar performance hit in JAVA. I have been dealing with Azure clients in JAVA that are sealed (final), and they are a pain to mock. I guess this discussion has motivated the MSFT team to mark these classes as final. I'm pretty sure that C# has easy ways to mock sealed classes, but JAVA doesn't seem to have them. I assume that the wrappers I wrote to overcome this will probably consume any overhead advantage that making the classes final achieved.
See my own reply about virtual methods. I don't know that much about Java APIs in practice and how they have their code written, but there is an extra issue in Java that methods are virtual by default, which just like inheritable classes, require runtime checks along the inheritance tree. Methods should have been final by default, and virtual just as an option.
Hey Nick, I love this video and agree in 'general', but I see one glaring side affect: Mocking...when a class is sealed, I can't mock it. I can extract an interface, inherit, and then so long as I'm careful to ALWAYS use interfaces, all is well again (and in general, I use a lot of dependency injection and use interfaces, etc.) I worry though that the framework may not allow that...and being able to mock things is quite important...thoughts?
Will the performance gains also be there if your sealed class inherit from an interface. Or does the type check not matter in that case. In terms of the performance he mentioned
It all reminds me of a thing that the C# designers did right: methods are non-virtual by default. Java is in fact worse, you need to explicitly make your methods final. I have learned earlier that virtual methods are slow, because they require the runtime to perform checks along the inheritance tree all the time. But to be honest, I was not aware of such a thing to be te same on the level of entire classes. But it is a logical thing, all for the same reasons. I think of course we can all criticize this, but the idea behind those languages started with inheritance in mind as a general feature. Mindsets where very different in the past. The idea of gaining the far best performance like native code was already given up anyway I guess. Flexibility is what counted.
Ive been making everything sealed for a while now but it is a chore to remember, especially for juniors. I think they should add an unsealed keyword anyway, that way they could have warnings on public types that don’t specify it explicitly.
You could always create a class template or edit the default visual studio class template as an alternative if that is something you think wouldn't harm your team's code
I agree, similar to how the public and private keywords work - you don't actually NEED both to write valid code, it's just good practice to specify weather something is public/private rather than leaving it up to a default value.
@@bluesillybeard It could be done in the same manner as nullable support was added to the project files. Legacy code could omit the setting and would work as previously to avoid breaking changes.
It would be nice if the language did that by it self maybe during the lowering process (not sure if that would make sense at this step) or even better making it sealed by default and having a config on the build process to change the behavior
they simply can not do that due to backwards compatibility. This is a long standing problem that affects anybody in software. For the dot net team it is no different than for yourself. As soon as you push something out the door, with focus on access levels, sealed or not, etc. your "customers" might be using your 'flaw'. Changing it is a breaking change. So the best they can do and have been doing for a while, is adding them as these checks and leave it up to you to treat them a certain way. Also the reason you should invest or use a proper IDE that makes you these suggestions for best practices.
There may be a class in another compiled assembly inheriting from the given class in current assembly. It's possible that they're linked only in runtime, so there's no way to know at compile time if the given class actually doesn't have any inheritors even if it doesn't have any in current assembly.
It depends if it's virtual, and if the type is well-known at compile time. If a variable is of type 'basetype' the type is not known, when it is of type "sealed type", it is known. Cause you are sure nobody can inherit. It's like removing the 'virtual' from the methods.
I was going to ask the same question. If we are using interfaces and the implementation is sealed, do we get the same perf improvement? I think I recall changes around perf for interface dispatch. Guess we could do test it ourselves :D
I avoid inheritance like the plage. I inherited (no pun intended) a project in 2010 where the previous developers made the mistake of relying heavily on inheritance, and it was an absolute pain to work with.
These kinds of easy optimization tips is my favorite type of content from you Nick.
Making the world a more informed place, 0.2239ns at a time
This doesn't make any sense 0.2239ns is essentially the time it takes to execute a single clock cycle on a 4Ghz processor. That's hardly enough time to even execute the return instruction. Aside from the suspicious benchmark numbers, all this optimization boils down to is removing the indirect call of looking up a virtual function address in the classes vtable and replacing it with a direct call instruction. At that point just make the method static / create a free function and you get the same effect.
@@davidparker5530 - we have instance members for a reason. Getting the performance of static without changing the code seems like a good deal.
@@anderskehlet4196 "performance" will be relative and YMMV. If you have a function that does some heavy computation, the overhead of a call vs indirect call won't matter at all (we're talking about a ns or two savings...). It makes no sense to just blindly seal all your classes for "performance". Take any real class that does something useful and seal it and measure how you get no real world performance benefit from it, this whole video is just a contrived example with a poor benchmarking setup.
@davidparker5530 That's a bit short sighted in my opinion, obviously in the grand scheme of things one shouldn't chase tiny improvements, but the relative difference is key here, if you implement this in all hotpaths in the application the change is rather dramatic and you won't even need a benchmarker to see itz you can see it with your own wet eyes. There are lots of places where C# is applied and Is performance sensitive. Try what happens when you're iterating stays in the teens of thousands with sealed vs open, it's huge for very tiny effort. Just write one more word!
Praise from a random viewer: I’m very much a reader, and if I expect an article and land on a video, I’ve already hit the back button. Your videos are not only excellent enough to transcend my prejudices, but to provide some income with perks as well. Many thanks from an internet stranger for your fantastic work.
I’m so the opposite and wish that could change lol (regarding the first part. love these videos)
Thank you for this video. I enjoy videos about the performance and features of C#.
By the way, I wanted to mention that in .NET 8 (I tested it on .NET 8.0.101), there are no significant changes in performance when running methods and changing arrays to spans. However, other cases still show the same performance differences. This means that it’s still important to seal your classes in .NET 8.
I hate this advice because I have managed to fix so many broken framework and library classes over the years by using inheritance. And entire avenue of bug fixing possibilities are quickly disappearing. These classes are not intended to be inherited and extended and these fixes are brittle but at least they're possible. I'd rather be able to work around an issue today and fix my workaround tomorrow than just be stuck.
I tend to say: a good quality of a library developer is to be aware that you might not think of what is needed NOW, but end users might come up with different ideas.
I like this advice for non-library projects. It's like free gains, and you can always change it in your project. But a good library author will allow users to extend it however they see fit.
totally agree. this approach will make libraries unrepairable in the future.
being so IQ 200, should not you contribute to those codebases instead of "fixing"?
@@blo0m1985 could be closed source.
There should be a setting which instructs the compiler to make all classes in a project sealed if they are not inherited. Could be usefull when you don't plan to roll out your code as a reusable library, which is most often the case.
Wish someone from development team could say something about this idea.
Probably easier to write a simple post-processing tool in something like Mono.Cecil to do that. You have to consider that in C# it is possible to load DLLs dynamically. Something might get extended in a different compilation unit, the existing tools haven't been designed for this case. But it's maybe a 100 lines in Mono.Cecil to make such a change.
@@julkiewitz Yup. Late binding very likely goes right to the heart of why there is a performance benefit to doing this. Apparently someone else has tested this and says virtual is the kicker, sealed/unsealed otherwise won't matter.
I solved this issue a long time ago by changing the class template in ReSharper/Rider to internal sealed. My goal was not to improve performance, but to not accidently exposing a API I don't indent to expose. It's nice to see that the performance is also benefiting from it.
Nice
This is an awesome idea. I shall also do this too.
If the C# design team believe it's a historic mistake that they cannot fix by changing the default behaviour then my first thought was why don't they just change the class template to stop it being as big a problem going forward?
Totally agree. Having been a Kotlin developer for the past few years, I've learned to appreciate sealed (or final) by default, and now I apply it as a matter of habit to C# projects. Whilst .NET doesn't implement sealed by default for classes, it does for structs, which is why you can't extend them. If you look at the IL for a struct, you'll notice they're marked sealed, and as .NET has no mechanism to 'unseal' a type, you simply can't extend them.
The other thing that would be nice is if C# had a language-level equivalent of Kotlin's 'sealed' (which is not the same as C#'s sealed), where you can create an abstraction that is only intended to be implemented within the same assembly. You can achieve this however by using internal constructors, so that it's not possible for extension beyond the current assembly.
As a frequent C programmer, one of the things I appreciate about C# is the ability to throw an OOP concept or trope like garbage collection overboard. There's definitely a place for them, but if I'm doing something like DSP it can be a performance factor of over a thousand just to clean up after myself instead (unchecked, unsafe), and work with buffers directly. So, I appreciate the occasional "do we always need this" introspection from the C# team.
As a kotlin dev, I really like the sealed nature of language, But coming from Java/C# background, I used to have a mindset that classes should allow for inheritance. I used to think that just encapsulation means a lot of objects running around in memory. It took me a while, a good mentor, and a lot of performance benchmarks to realize the runtime benefits far exceeds the memory overhead, and also that a badly designed inheritance will actually consume more memory than encapsulating multiple objects.
Great video as always. I really love all the benchmarks numbers that you show to validate your tips.
Badly designed inheritance has a ton of disadvantages, strong coupling, hard to understand, hard to maintain.
Sometimes impossible to refactor, because 20 different inherited classes are strongly coupled, and minor changes, can lead to subtle (but devastating) behaviour changes.
In 90% of the cases:
Prefer composition over inheritance
@@fr3ddyfr3sh Absolutely agree that you should prefer composition over inheritance in 90% of cases. The only problem with this mantra is that most people don't understand the other 10% of cases.
You'd think the runtime could handle this. The jitter knows if any classes inherit from this one, and if additional assemblies are loaded at runtime then the affected code could be re-jitted.
It probably wouldn't be a good idea to run this at JIT due to the performance impact of that analysis. Much better to evaluate at compile-time and seal all classes without members that inherit them.
@@ZintomV1 You can't evaluate that at compile time because you don't know if an external assembly is inheriting from that class.
Yeah! Since I played along with Kotlin some years ago, I got used to sealing all my classes in c#.
It is not only a question of performance but, first of all, good an robust design of my code.
In a comment I made on one of your previous videos, I mentioned the preferred "composition over inheritance" pattern and got so much pushback. Glad to see you mentioning this as well!
I've been using c# as my main horse for over a decade, even wrote transpilers to IL and still learned something new today, thanks!
Like every advice from every "guru", sealed classes in every language are usable in small number of use-cases.
If you can't inherit a class then every framework that is depending on proxy design pattern (for creating "sugar" classes) will fail and the list of frameworks is very large RestControllers, ORMs, DI etc.
And why those framework will fail ?
Because all of them depend on proxy design pattern, which can be implemented in three ways
1. as a pair (interface and class) or
2. as inheritance of the class annotated with metainformation.
3. provide factory methods for every sealed class, that will be use by the frameworks (mostly use in DI)
If you are using sealed then you must 1 or 3
This means every seal class needs to have "dummy" interface in order for the framework A to create "the sugar" sealed class or factory method
Congratulations, instead of one "sugar" class A, know you have sealed one "sugar" class A + dummy interface IA / factory method and your code base is now twice as big.
Don't get me started on ORMs, there is a high change that the amount of SQL generation will be double or you will wonder why ORM always generates insert statements.
But this is just the software developer perspective
From library POV - in order to fight sealed word, the libraries have to provide compiler hooks and now instead of Microsoft or your fav language company you have to wait 3-rd party company to fix your compiler bugs
Classes shouldn't be sealed by default, because they are almost always useless
Everything you said can be done with interfaces and composition in C# :)
@@nickchapsas yes
However,
if project with unsealed classes has 1000 classes
then project with sealed classes will be at least 2000 classes
And that is the price to pay for using sealed keyword by default
I love the little Hello World eastereggs! Such a personal touch
I jumped in hoping for a great performance boost after sealing ~1800 classes in my project. Perf increased by 0.3% overall. So kind of a mixed bag... Ah well, thanks for covering the fundamentals
Wouldn't the compiler optimization already know which classes aren't ever inherited in the project, thus quasi-seal them for the build?
I'm curious if this has any effect after the code is converted using IL2CPP as is often the case when making final builds on Unity.
Really interesting and awesome never the less!
I even wonder, if this is the case for Mono in the first place.
tactical dot in case someone will be less lazy than me and tests it: .
Basically no difference in 2021 LTS as the .Net versions supported are Standard 2.1 and .Net 4.0. Neither of those have the specific optimization being talked here in the IL (using direct call instead of callvirt).
@@Xankill3r Thank you for pointing out! Makes sense.
@@Xankill3r Thanks!
I totally agree with this, and I was thinking maybe change the New Class template that Visual Studio uses to create sealed classes?
Nice and short as always, great video
I like the numeric values you use as examples.
6:10 it's easy to fall into a pit of misconception, so I'll drop my thoughts there
Notice those ZeroMeasurement warnings down there, those say that method duration is too small to be taken into account, often times because the method is inlined and its execution takes virtually the same time as calling an empty method, meaning it's "the fastest you can get" when comparing vs calling a method. So it's not actually "38x" faster or "1000x" faster, it's "the fastest you can achieve".
I had run to run variance between the executions so in some cases it was ZeroMeasurement and in some it wasn't so I left that comment out
This is probably an effect of the methods not being correctly aligned for an ideal implicit inlining scenario, but either way if you do notice a zero measurement warning, you should assume that it's happening sometimes.
Remember that given how inlining isn't perfect, were that same zero measurement warning to appear every single time, the performance could easily not be as high in as many as half the cases in a real world scenario. This is microbenchmarking and sets up and ideal scenario to juice out the maximum performance, isolating as many external variables as possible.
TL;DR always worth noting that *even once* you get a zero measurement warning.
@@AlFasGD obviously you're not wrong, and the information you've posted is super interesting to me so thank you! But I think the benchmarks here are just to show clearly that there IS a performance gain for zero cost, the video is explaining why sealed should be default and demonstrating that there is ANY performance improvement is a good enough argument to support that.
@@gnack420 I agree but I also believe that it's generally good to perform a general fact check
Probably your best advice until now. Thanks!
Can you do a video about the inheritance anti-pattern?
Wow! I had no idea the performance difference that existed between. I'm really glad I watched this!
You noticed in this video that inheritance now is considered an antipattern.
I tried to find some nice examples of how we can replace inheritance with composition, but I couldn't find some nice examples.
Could you please make a video on how we can refactor code with inheritance to the composition?)
I will make a video on the topic. It's not as simple are replacing class extension with interface implementation. It goes deeper than that
Hey Nick
I'd like to say thanks for your video and I have a question. The performance sag in your example could be a consequence of the virtual method table (not just because of "sealed" class). If you try to remove the "virtual" word you will see the other results.
It does not negate the fact that type casting and some other operations are faster with sealed classes, but I think the "sealed" is not something that always makes performance better.
Actually was wondering about that and tested it ... if it's not virtual its just the same speed ... so sealed/unsealed has no impact on none virtual methods.
@@zer0K3lvin Thanks for testing that, I was wondering as well!
Sealed can be very annoying too. Like when you want to modify third parties code. Extend it and modify one function you want. With composition you cannot input it into functions that take the original class, only that take an interface, so you have to copy all the functions too that take or depend on original class.
Exactly....it's happened to me a few times.
That's a feature, not a bug. You're going to be doing that so so much more rarely than actually working on a normal class. Allowing instance means that normal code now needs to maintain invariants even in the face of fairly hard to predict code replacement. Your subclass needs to be tightly coupled with the base class, and needs to understand what the actual class internal api is, because if the base class ever changes anything, you're likely to encounter all those hidden assumption on a bug report.
The effort of rarely copying a few method declarations is so much smaller than the effort of dealing with unnecessarily flexible code, especially if anyone was ever so unwise as to abuse that flexibility.
Inheritance as a form of method interception is a recipe for unmaintainable spaghetti code.
New coder here. What I took away was “sealing classes not meant for inheritance = better performance”.
just use records, they are sealed
If you design it "not meant for inheritance" seal it to document "it's not meant for inheritance".
This may be great in theory, but in practice when trying to test either legacy systems or library without a good testing strategy. Sealed classes have stopped me from writing unit test many times. Especially when you want to implement tests without changing legacy code.
For new systems it might be more applicable, but they will be legacy system one day. So how do you want to leave the code for future developers.
This is a design flaw in your code. Third party services/libraries should be abstracted using an adaptor that conforms to an interface that you've written. You can then trivially mock the adaptors in your unit tests. There's no need for those third party libraries to be unsealed if you are properly abstracting your dependencies. You should also avoid injecting concrete types as dependencies whenever you can.
@@gnack420 Well that is also a question about theory and practice. Yes I know all about abstracting dependencies. I have 10 years experience with looking at old code that either only is the layers of abstractions or looking at code that has no abstractions. And the one thing that made me utter profanities more then others is sealed classes. When trying to test.
And it’s a lot easier to end to end test code without infrastructure when third party libraries do not use sealed on their classes. Yes I can write a lot of unit tests with my abstractions but if I send the wrong property in the wrong argument in my implementation. The only thing the fast code will produce is more problems for me to clean up in production if QA don’t catch it.
This is correct Magnus. Unit testing is one of the shortcomings of sealed classes. And being told I have to extend my object model to support unit testing because I sealed all my classes, flies in the face of everything we learned about object data models, and the quest for simplicity and extensibility. I admit there may be outlier use cases in which sealing all my leaf classes is a necessity or a sound design choice. But for the vast majority of object models in the world, even considering C# little semantics flaws and idiosyncracies, sealing a class should be a structural decision about the object model. Not the result of an arbitrary generalization to gain infinitesimal performance gains on an application that has no performance issues in the first place, or where those performance issues are understood to live somewhere else in the code. This wasn't one of Nick's best days.
Three examples in which I would want to seal my class:
(1) I'm writing a class with value-type semantics.
(2) I'm writing a code generator for an engine or interpreter which relies on specific types being consumed.
(3) I'm writing a utility math class composed entirely of truisms. (On this special case, extending behavior is best achieved through composition).
All in all, Microsoft advice in the sealed keyword documentation page still stands. In short, do it only if it makes sense. Avoid it otherwise.
@@MagnusBertilsson I don't understand. When unit testing a class, you're supposed to test the methods of that class, no? Ergo, instantiate an object of the class, call its various methods with various parameter values, analyze the results. Sealed or no sealed class makes no difference, legacy or brand new class makes no difference.
Absolutely love this video. Thanks a lot for this video.
I think if ide(s) like Rider and Visual Studio add this feature (Adding sealed keyword in snippet code of Creates a class declaration)
It will be reasonable and more practical.
Do you know if there is a setting in VS? When I create a new class, it defaults to internal.
With the new analyzer you can make rider remind you and I'm pretty sure they will be adding a refactoring too
Thank you Nick. You just made me christmas
Change your class template in your IDE to include sealed by default (and make them internal while you're at it).
Just keep an eye out when you do updates. I've made template changes in VS before and updates will wipe them out.
The performance improvement on the methods suggests that they are being inlined, and the performance improvements on the is / as would be because it can directly check the type instead of checking if the type being tested is assignable from the type of the value.
This is not a "design flaw". This is a historical artifact of object oriented languages where classes are designed to be inherited by default. This also permit classes in libraries to be extended. Clearly over the years there's been a move away from object orientation with better methodologies of extending features. C# was based on Java and other OO languages at the time.
Hmm, inheritable by default, but methods not virtual by default.
@@rauberhotzenplotz7722 - “encapsulation” is still one the 3 benefits of OO though….that’s one of the points of inheriting non-virtual methods.
@@matthewwood4756 You can accomplish pretty much anything that is achievable with inheritance with composition instead, and it is usually more flexible, cleaner, and separates concerns better without sacrificing encapsulation.
@@timseguine2 - I’m not necessarily disagreeing with you, but using true OO principles of multiple inheritance, the same can easily be achieved that more readily/accurately “models” real-world problems/designs/solutions. “Composition” is the answer/pattern that has naturally evolved due the mainstream implementation/acceptance of single-inheritance languages/compilers.
@@matthewwood4756 No. Classical OO often confuses the object hierarchies' isA relationship with the isA relationship of the thing they are modelling. These aren't in general the same relationship from a mathematical standpoint, so it doesn't make sense to model them like they are the same thing.
The argument against inheritance comes historically from the Liskov Subsitution Principle and has absolutely nothing to do with single versus mutiple inheritance languages.
Or put more charitably, you are reversing cause and effect
Yes in languages with single inheritance there is additional motivation to favor composition over inheritance, but that was partially the motivation for them deciding to force single inheritance in the first place.
Loved it. Thank for sharing!!!
I usually always avoid implementation inheritance. So I have been doing this for a while.
And even the general rule is: either design for implementation inheritance or forbid it. I usually design for interface inheritance, so yes, of course I seal my classes since I am not designing them for implementation inheritance.
Well you learn something new everyday! Thanks Nick
Excellent article Nick thanks , I wonder if this apply for records too.
Hey Nick, thanks for the great video. I have two questions. 1. Is the performance difference only in .net 7, or is it in older veresions as well (.net 6, 5, .net core, .net framework)? 2. Usually there's no such thing as free lunch. In the video you displayed making class sealed (if possible) as an absolute positive, with no negatives. Are there some known drawbacks to doing it? Thanks :)
1, Yes i applied to previous versions as well. 2. The "negative" is that you can't inherit from it anymore but if you were never planning to do so then it's just positive.
@@nickchapsas Thanks for the quick reply. Most of the classes are not inherited anyway, so this might give us some performance gains. I'll give it a shot.
@@nickchapsas Could you please share your opinion about how to test such sealed classes? could it be measured as "negative" point of this approach? Because I can see a problem with some legacy or some projects with non-structured test strategy where such "sealed by default" might lead to issues when we try to mock them or create a stub for such classes (if there was no adaptors which would allow to mock them instead of the classes directly). Thanks!
@@nickchapsas I second this Nick, could you show a fiddle of how you unit test those sealed classes?
I agree. The largest accurately measured specimen of the elephant seal was a bull which weighed at least 4 tonnes.
thanks Nick, great information.
Always wondered about this , thanks!
I tried that, but notice you can't seal some entities when using dotnet EF because they use a Proxy for the DB
Definitely, that’s how EF and also NSubstitute work.
But the latter will most likely be used on interfaces
True but those are not the type of calls that you expect in a inner loop as they are io bound , so nanosecond improvements won't matter anyway.
Yet another reason to not use EF in my book. I'm an EF survivor and have been EF free since 2010. :)
Eye opening video. Is same is true for sealed method ?
We really need a way to specify different c# 'versions' so features can be changed retroactively without having to worry about backwards compatibility. Any new project will use the most up to date recommended version and if an older project is upgraded, they can change individual settings to reenable compatibility modes. I think that would be awesome.
@@MiningForPies no this is not what i mean here at all.
i mean: instead of classes being implicitly open by default, and you have to seal them with the sealed keyword, in the recommended version classes would be sealed by default and there would not be a sealed keyword, but rather an 'open' keyword.
the same applies to other features, for example _ in lambda is a discard, even in single argument lambdas (which is not currently the case because of backwards compatibility concerns by the .net team)
and there's a whole list more of similar features.
there are already programming languages that have a 'version' model like this. nick has even mentioned it in a video about it a few months back. (though i cannot quite remember which one)
You can specify the language version in your .csproj files.
@@gnack420 are you people even reading what i am asking for? cause apparently you dont.
Wtf are these replies people don't read 😅
I commented exactly this and saw this comment. Do you know what the stance of the c# team on this is? It should have been discussed at least right?
So I had a look as to WHY, and it's mainly because a call to a VLT can be being turned into a direct call by the compiler, which makes sense (still not idea why the 'is' and 'as' operators run faster though) [edit]Same idea, the runtime doesn't need to walk the inheritance hierarchy.[/edit]
I suspect that this will have less of an impact if you have a deep inheritance tree with all of the leaf classes marked as sealed, because any method that at runtime can accept the base class will still have to do a lookup. That said, if people stick to composition > inheritance that shouldn't be an issue ;-)
Interesting! Where could I find more info on the why?
@@TheKevinKaimon It''s in the linked github issue :)
Does compiler really can turn overriden and hidden functions with direct calls with ease? Or more advanced inheritance based trees are still at tables? I know that sealed in real life barely makes performace impact.
Thank you for your videos. This is very useful for me.
Interesting....One case you need to be careful is if you have created a nuget package / dll and you seal a class that would be very useful for your user to inherit from. This has happened to me and it caused me alot of pain.
Absolutely agree. I cant imagine how i could survive till now, if i hadnt possibility to extend/inherit all kinds of 3rd party libraries.
Is the difference only measurable in newer versions of .NET? Or will it be worth sealing all classes in .NET Core 3.1 as well?
They apply to .NET Core 3.1 too
Even older than that, full framework can take advantage of converting virtual calls on a sealed class to direct calls.
@@protox4 not really. The results on Full framework(.NET 4.8) show almost no difference between sealed and open classes
It's treating virtual functions, like non-virtual functions. You have this optimization everywhere.
@Nick Chapsas what about public class XXXController : ControllerBase in web api?
good point
Any good talks/examples on "composition over inheritance"?
Rider has an inspection severity setting for this outside of .NET 7 under Settings / Inspecting Settings / Inspection Severity / C# / Class can be made sealed (non-inheritable)
I've been making my classes sealed by default for years now. I don't do it for performance reasons (though every little bit helps!), I do it because I don't like to use inheritance when I don't have to. "Prefer delegation over inheritance", etc. I only wish VS made it easier to change the default class template, so I could make those changes more quickly and easily.
Ok this will be interesting. I have never used sealed before.
Ok this was totally worth it. Learned one more thing today. Thanks for the vid Nick, great job as always :)
I think right now it's needed to be included into linters warnings by default like "The class %ClassName% is not inherited anywhere and can be made sealed". Is there any suggestion for this on JetBrains YouTrack?
The Microsoft Documentation from April 2022 says: DO NOT seal classes without having a good reason to do so.
"Sealing a class because you cannot think of an extensibility scenario is not a good reason. Framework users like to inherit from classes for various non-obvious reasons, like adding convenience members. See Unsealed Classes for examples of non-obvious reasons users want to inherit from a type. ..."
Also Mocking is a Problem.... thoughts?
I clearly disagree with that as well as many developers in the C# and .NET team. You can clearly see that MS themselves don't follow that rule. Docs advice is designed to be beginner friendly. There are tons of advice in there I disagree with.
@@nickchapsas Ok Thanks!, I actually think the same
You do have an option to 'extend' sealed classes. You can do it with extension methods!
The same article you are referring to though also ends with: "Portions (C) 2005, 2009 Microsoft Corporation. All rights reserved." and comes out of a book "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition", Published Oct 22, 2008.
We are back to that since 2000 to 2010.... the developer landscape has changed significantly and this is just one of many 'viewpoints' that have changed/shifted.
There is definitely a big issues too with not sealing your types. Because it implicitly means that you as a developer, especially if you write a library, would have to put a lot of defensive code in to ensure that outside users implement the types different or wrong.
Personally I also favour using extension methods even on types that are inheritable if possible of course. If I just need to add a helper, like addind an AddRange() or some display or factory method or something. Rather then to deal with the inheritance and having to implement then everything the thing needs to have.
I always have to think back to one time I had to do it, having to add a helper to a certain type. I had to do it by inheritance but due to other bad design practices by the api designer, I was forced to implement 30 other methods.
@@paulkoopmans4620 Extension methods currently have a ton of limitations though. For example, you cannot do something as basic as adding a property to a class using extension methods.
Yes perhaps this is eventually coming but it’s probably at least 2 years out from being included in the language.
@@bass-tones Yes I understand that extension methods are only able to do anything with the object by using its public interface. The name 'extension method' is a bit deceiving and I realize I am actually not 'extending'. The class does not change by creating extension methods.
A sealed class though, which this topic is all about, will never be able to get any extension whatsoever, but at least you can add some helpers like maybe an AddRange() method that calls the single Add() method provided by the library. Or maybe a Search() or ToEnumerable().
This is also still where encapsulation might also help you out, IF and ONLY IF the designer of a library actually has done it properly and depends on interfaces.
If you ever wrote an external facing api (not webapi, but as a library I mean) then you know why you would try to make pretty much everything internal and the stuff that is public you very likely want to seal. The actual classes, from a library perspective, that NEED TO BE extendible by inheritance as the only option in order to be useful to the user, is probably near zero.
Sealing your classes and guarantee yourself that your classes do 'exactly' what you expect them to do far outweighs the pain, risk and cumbersomeness that you would have to add if you leave your classes open for inheritance and your code consuming then would have to have all kinds of defensive code and checks in them.
I Think that with the new feature of C# "Extension everything" this could be adopted in very fast way
Hi Nick, thanks! I didn’t expect sealed to make such a difference.
Any chance of the analyzer being released to older versions than .net core 7?
Analyzers depend on sdk version. You can use the new sdk to target older versions just fine. Worst case you could multitarget, e.g. for nullability, but that won't be necessary here.
I was a little skeptical. Inheritance is a big feature to make a general statement about throwing it away. But interfaces exist, so having most classes sealed and interfaces available could give the performance boost of sealed while allowing for people to make their own API compatible classes. The main concern is that if something is missed and there isn't an interface or some other way to customize that part of the API and the relevant class is sealed then some dev somewhere is gonna break out reflection and do some sketchy hacks to get around it.
How about static classes vs internal, sealed, private ?
The performance benefits also seem to apply (not fully though) for an "open" class that uses sealed override methods. (public sealed override ExampleVoidMethod() { })
Only on the method invocation itself. Having the class sealed applies them to everything for instantiation, to type checks to method calls etc
Apart from performance and clarity reasons, sealing the class which implements IDisposable saves you from not implementing dispose pattern
I couldn't agree more with this video. I check for this in code reviews and insist that things be sealed unless explicitly needed otherwise.
I feel like many things, like Sealed being the default, could be changed, similar to NRT(Nullable Reference Types) - by adding a setting in the .csproj file.
true
true // This is currently not even a feature, but it hopefully will come soon.. and then potentially add a wrapper tag:
2022 // This would then set all the default values (like NRT to enabled, both of the preceding values to true, and so on..)
C# is an aging language and has a lot of flawed design decisions. I feel like, at some point, this needs some way of being addressed. Why do we, in 2022, have to live with poor design decisions made in early 2000s when we don't run _any_ code from that era?
That said, Roslyn Analyzers are a great alternative to fixing the flaws. It keeps the flaws in, but makes it impossible to use them
you personally might not. But I know for a fact that the companies I have worked in since 2005 all are still alive.
There is a fair chance that some of the original code is still there and running.
@@paulkoopmans4620 They could still easily interoperate - It would still compile to the same lowered code.
It is however a definitive problem with the consistency of the code across multiple projects, but I'd argue that's also an issue with NRT
@@modernkennnern I was just commenting on your claim that in 2022 there is no code from that era running anymore. :)
Again given that the companies that I wrote .net 1.0 code for, in the 2000's are still around, I do want to believe there is still old code bases around.
Now this particular 'flaw' of not sealing by default is even prevalent for code created last month.
And I agree is a problem and that it is hard to break the cycle.
I see what you are trying to say with comparing it to NRT. Yes; they are solved the same way. But also both of these suffer from the same problem. If you have some old library code sitting somewhere in your massive code base and you never get to address it, it still needs to successfully compile today. Therefore the compiler has to generate instructions that give the same behaviour today as it did 20 years ago. Which is why both NRT and this one will just generate warnings but will compile regardless.
The reason why such a flag in a project file could not work is because for some new parts you finally want to start with classes that are sealed by default but this dark grey area of your massive project should not generate a sealed class, and unfortunately both of the lines are identical and the compiler could not tell which one to generate as which type. I appreciate that for a net new project and/or solution it could. But the having the warning and your IDE treating it as such is frankly the same experience.
AWESOME content!
Do this rule of sealed classes also work for the old .NET Framework? Is there a gain in performance there as well?
Yes the performance benefits have been there since forever
Question I always asked is why the tooling, examples, compiler, whatever don’t do this by default?
Great video, as usual. One point I noticed in your tutorial (and Stevens issue) is that both the sealed class and the open class are themselves inheriting from a base class. Would the same hold true if both of these classes were not explicitly inheriting another class?
It would yes
5:50 this is nonsense, I'm not sure if you talk about this in other videos. The fastest measureable time in theory is one CPU cycle. For a modern 5GHz machine that would be 0.2ns (you, too, have a 4.9 GHz processor). If the benchmark returns a value smaller than this, there's a measuring problem and indeed, dotnet benchmark also warns you about this right below the table, in red: "This method duration is indistinguishable from the empty method duration" and further more, the median is 0ns, which means, half of the benchmarked values were 0ns. So "1000times faster" is nonsense. To measure super fast code you can not purely rely on the benchmark framework. It's a bit tricky. You might need multiple evaluations in the benchmarking function. The benchmark values always contain a bit of noise. The OpenVoid method takes 1 CPU cycle and the OpenInt method takes 4 CPU cycles. So you cannot be faster than openvoid because you cannot be faster than 1 CPU cycle.
But even the "warning" message proofs, the 'sealed' versions are faster. The warning is not produced for all versions. So it's significantly faster, this was what had to be proofed.
And it says "It's close to a method doing nothing", which is correct, cause a method just returning a constant integer, is the same, as a non-virtual method returning nothing.
You are correct, it's not exact you cannot say it's 1000 times faster, but you can say "it's faster" and probably at least 10 times faster.
8:26 I can see why you did that ;).
Hi Nick, what do you say about the argument that this behaviour violates the open closed principle?
That it doesn’t. I’ve answered this already in the comments
Awesome man!!!
A beginner question: I have a Visual Studio project in .net core 3.1. Will these benefits apply for me in my .net core project?
Absolutely
Seems like the performance gain could be a compiler optimization without having to seal classes manually. If there are no extensions of the class, the compiler should be able to know that.
Not when the classes are public. Anyway, I expect some edge cases could become problematic with such optimizations, so it sounds better to expect programmers to make that explicit in their code.
How? Your module can be loaded dynamically at runtime and linked with other code that calls it?
@@davidparker5530 I didn't test it, but I'm thinking of reflection, for example, but I'm sure there must be other cases too. I can't post links without angering TH-cam, but there's a "How to dynamically create a class?" thread on Stack Overflow that has code samples.
Is there a a video about inheritance vs composition in classes in C#?
Is there a way to seal the Program class and still use the Top level statement?
I actually don't know the answer to that but I don't think so
@@nickchapsas I believe you can put "sealed partial class Program { }" in your Top level statement class
@@ghidello It works ;) Thx, demons and classes should be sealed forever
Can't they add open keyword and then have a optional property in project file to say classes are open or not by default? something like nullable or implicit usings. this way it's backwards compatible, and if you make new project template classes to be sealed by default it gets adopted more easily and just by that option being there it makes c# users search and know what the benefits are.
Exactly what I was thinking
how? Maybe I am not seeing what you mean?
the point would be that some old code from the 2000's like: public class X {} , stays a public and open class as you cannot infer whether or not it was intended to be sealed. This is the whole reason why you would have to 'close' it by adding it in the first place.
They need to keep the compiler, runtime and JIT separate from IDE. I have said this in other videos too why adding a new keyword is not as simple as it sounds. Writing a lexer and parser is one thing. And to change it' to support a new keyword is ok, updating documentation also. But what if anybody out there has a class called open? or a variable?
This is the reason the team has been favouring re-using existing keywords (like this in an extension method). They are guaranteed that nobody is using a keyword for something. And even if somebody did... they are doing with an @this.
With the warning and the ability for you to treat it as a warning or error is the best they can do.
@@paulkoopmans4620 third party libraries imported from NuGet are already compiled it doesn't matter what their source code says.
@@gnack420 Of course it doesn't matter for already compiled libraries from nuget, or any source for that matter. I did not mention library in this response at all. The obvious problem is if you want/need to recompile old code.
Btw; library obviously means two things; there is libraries (which we should call packages) that you can use from sources like nuget or proprietary downloads from a provider, but also every one of your 'project' in your 'solution' compiles to a class library as well.
And I get the sense that a lot of people seem to be fortunate and only work on relatively new stuff, and that's great for them, but from experience I know that other people do have this battle in their day to day jobs; having some lingering code in the project that never gets touched and is over a decade old. And for this particular issue it could actually affect code created a couple of moths ago to be fair.
it is these cases where the .NET team is making conscience choice to keep on compiling 'public class MyClass { ... }' to an 'open' or 'unsealed' class because they simply cannot guarantee it would not be inherited somewhere. So they are left with basically keeping it the way it is.... open by default and close it when you want it... because of backwards compatibility. And re-using the paradigm that they have been for a while... having the compiler spit out warnings that proper IDE's like Visual Studio, Rider, etc. can catch and help the developer take actions.
Just imagine if you would work in a company with tons of legacy code and all of a sudden your build pipelines would fail building your code and you were now tasked with 'fixing' or 'patching' that, just because the compiler is now requiring you to add 'open'?
Or you wrote a package last year that you are selling and you have 5000 customers using it. Your package is designed around a concept where your customers are allowed to inherit from a handful of classes. You run it through an updated compiler, which then closes all these classes that you actually intended to be open? Of course you would fix it... but the point is that the behaviour change of the compiler forced you into changing your code. Amplify this issue and that change might affect thousand's or tens of thousand's.
I wonder if there is a similar performance hit in JAVA.
I have been dealing with Azure clients in JAVA that are sealed (final), and they are a pain to mock. I guess this discussion has motivated the MSFT team to mark these classes as final.
I'm pretty sure that C# has easy ways to mock sealed classes, but JAVA doesn't seem to have them. I assume that the wrappers I wrote to overcome this will probably consume any overhead advantage that making the classes final achieved.
See my own reply about virtual methods. I don't know that much about Java APIs in practice and how they have their code written, but there is an extra issue in Java that methods are virtual by default, which just like inheritable classes, require runtime checks along the inheritance tree. Methods should have been final by default, and virtual just as an option.
Hey Nick, I love this video and agree in 'general', but I see one glaring side affect: Mocking...when a class is sealed, I can't mock it. I can extract an interface, inherit, and then so long as I'm careful to ALWAYS use interfaces, all is well again (and in general, I use a lot of dependency injection and use interfaces, etc.) I worry though that the framework may not allow that...and being able to mock things is quite important...thoughts?
You’re not mocking classes you are mocking interfaces and abstract classes which can’t be sealed
Will the performance gains also be there if your sealed class inherit from an interface.
Or does the type check not matter in that case.
In terms of the performance he mentioned
Interfaces are fine
Interfaces are not inheritance, but just forcing you to add set of methods with specific signatures to your type.
obrigado. agora que você postou as pessoas devem me ouvir mais 🤣🤣🤣
Great video.
It all reminds me of a thing that the C# designers did right: methods are non-virtual by default. Java is in fact worse, you need to explicitly make your methods final. I have learned earlier that virtual methods are slow, because they require the runtime to perform checks along the inheritance tree all the time.
But to be honest, I was not aware of such a thing to be te same on the level of entire classes. But it is a logical thing, all for the same reasons.
I think of course we can all criticize this, but the idea behind those languages started with inheritance in mind as a general feature. Mindsets where very different in the past.
The idea of gaining the far best performance like native code was already given up anyway I guess. Flexibility is what counted.
Is there any performance gains in the regular .NET Framework? Say in the latest 4.8 version?
Yes
@@nickchapsas Do you know if it's about the same amplitude?
Hmmm.. Why won't they make it configurable, like with nullables. So you can have sealed by default in your assembly?
Where do you got .NET7-RC2, on the download page I see the latest preview7 as the latest available for download?
Same here. I was going to make the same comment, but I'll stick it here and hope YT notifies me too if you get an answer 😉.
+1: Where do people get RC1/2 from?
Came to ask the same thing
Those example int method numbers: Ah, I see you're a man of culture as well :D
I see the performance differences, but isn't it better, when other people can inherit from your code and add/customise it to their needs?
Only if you designed your code to be customizable for their needs
Hi nick, pls consider design patterns also in upcoming videos. Mainly DDD and eventsourcing. And whats ur opinion on aggregate roots
This is a C#/.NET channel - there are already plenty of good videos on design patterns.
I think he did videos about that using MediatR
You have great videos on that topic - on Milan's channel th-cam.com/users/MilanJovanovicTech
@@thecodeman_ thank you 😊
Ive been making everything sealed for a while now but it is a chore to remember, especially for juniors. I think they should add an unsealed keyword anyway, that way they could have warnings on public types that don’t specify it explicitly.
Why not just reuse the existing virtual keyword and a project setting to default to sealed classes?
You could always create a class template or edit the default visual studio class template as an alternative if that is something you think wouldn't harm your team's code
I agree, similar to how the public and private keywords work - you don't actually NEED both to write valid code, it's just good practice to specify weather something is public/private rather than leaving it up to a default value.
I suspect if we make sealed a default, a lot of legacy code would break, yes? A code analyzer is the best way forward.
@@bluesillybeard It could be done in the same manner as nullable support was added to the project files. Legacy code could omit the setting and would work as previously to avoid breaking changes.
It would be nice if the language did that by it self maybe during the lowering process (not sure if that would make sense at this step) or even better making it sealed by default and having a config on the build process to change the behavior
they simply can not do that due to backwards compatibility. This is a long standing problem that affects anybody in software. For the dot net team it is no different than for yourself. As soon as you push something out the door, with focus on access levels, sealed or not, etc. your "customers" might be using your 'flaw'. Changing it is a breaking change.
So the best they can do and have been doing for a while, is adding them as these checks and leave it up to you to treat them a certain way. Also the reason you should invest or use a proper IDE that makes you these suggestions for best practices.
There may be a class in another compiled assembly inheriting from the given class in current assembly. It's possible that they're linked only in runtime, so there's no way to know at compile time if the given class actually doesn't have any inheritors even if it doesn't have any in current assembly.
I understand you both points of view and I do agree. I was just saying that I would like to somehow have this as default behavior of the language
Great video, thank you! I expect there would be a lot of comments from people who oppose sealing everything :D
What about unit test frameworks? They use this for mocks.
Mocks are based largely on interfaces and abstract classes. You can't seal and abstract class or an interface
You should be injecting interfaces, not concrete types, where possible. Then you can always mock.
Can we seal endpoint classes like Controllers?
Hi, Nick!
Is it also useful to add to records?
Ofc. Records are just classes with some fancy hidden methods
I'm wondering if there is performance difference when two private methods call each other in a sealed class vs a not sealed one.
It depends if it's virtual, and if the type is well-known at compile time.
If a variable is of type 'basetype' the type is not known, when it is of type "sealed type", it is known. Cause you are sure nobody can inherit. It's like removing the 'virtual' from the methods.
Is there a way to get this analyzer work with Unity?
It’s a pretty simple analyser so I am assuming you can yoink it from the repo and use it as a nuget package
Does this also work for interface access of sealed classes at all, or do you have to know the underlying type?
I was going to ask the same question. If we are using interfaces and the implementation is sealed, do we get the same perf improvement? I think I recall changes around perf for interface dispatch. Guess we could do test it ourselves :D
Probably not, cause the real class to call is not known at compiletime. It could be just everything. It's a compile-time optimization.
I avoid inheritance like the plage. I inherited (no pun intended) a project in 2010 where the previous developers made the mistake of relying heavily on inheritance, and it was an absolute pain to work with.
Do the performance benefits apply for .NET framework classes as well?
Yes