B.GetType() == typeof(A) will fail when inheritance is in play. B is A will still work.
@nickchapsas2 жыл бұрын
That is correct, I should have mentioned the behavioural difference in the video
@Krilllind2 жыл бұрын
Very true, but for most modern development I would suggest using composition over inheritance and then this won't be an issue 😊
@fkeyzuwu2 жыл бұрын
really? i did this a few days ago and didnt have problems..
@realtimberstalker2 жыл бұрын
In this instance you could use the IsSubclassOf or IsAssaignableTo methods.
@IceQub32 жыл бұрын
@@Krilllind if you want to check for interface implementations you need this, or an example you want to check if an object implements IDisposable, using the class object reference wont work, an you will need somthing else.
@masonwheeler65362 жыл бұрын
I'm not surprised to see that the typeof comparison is the fastest. This is exactly what I'd expect, because it's checking an exact type match, one value against one value, rather than a type hierarchy match of a chain of values against one value. (And even when it only checks the first value in the chain, it's still using code that's meant to traverse the entire chain, so you 'd expect there to be some overhead there.) The tradeoff here is that there's no polymorphism. It will match objects of type Person, but not objects of types that inherit from Person, where the *is* keyword and any of the casts mentioned in this video will match subclasses as well.
@cala-br2 жыл бұрын
To add to the Match Casting, if you invert the condition you can use the casted variable outside of the if scope. That is if (StaticObjects.Nick is not Person nick) return; Console.WriteLine(nick.FullName);
@jonathandunn93022 жыл бұрын
Took me a while to get used to this syntax but it's really useful in some instances!
@cala-br2 жыл бұрын
@@jonathandunn9302 Yup, it really comes in handy
@cala-br2 жыл бұрын
@@guiorgy You're welcome!
@evancombs51592 жыл бұрын
Please don't do this, it reduces code clarity.
@jonathandunn93022 жыл бұрын
@@evancombs5159 I disagree, it's removes the need for an explicit null check and reduces nesting, those things alone improve readability imo
@alexclark67772 жыл бұрын
Would be interesting to see this in SharpLab to see if the lowered code is different. I would assume that the comparison of b.GetType() to typeof(A) is fastest because it's doing an exact comparison, whereas B is A is checking to see if B is the same OR a descended type of A. Properly interesting test - this is the kind of C# trivia that I love, and keeps me coming back to your channel.
@ricardopieper112 жыл бұрын
Indeed B is A checks for subtypes, while GetType() == typeof(A) should be just a pointer equality check, or whatever ReferenceEquals does. .GetType() should be very very fast because it always returns the same type instance/same ptr.
@ToadieBog2 жыл бұрын
Nick, seems like I always learn something new with your videos. Thank you so much for your content and your contribution to the community. Great stuff!
@zhh1742 жыл бұрын
What about this? People.Select(x => x as Person) .Where(x => x is not null) .ToList() I think this will be the fastest because the all the others essentially cast the object twice. In hard cast also you first safe cast then again you hard cast
@thehuggz-i9k2 жыл бұрын
Benchmarked that for ya: | Method | Mean | Error | StdDev | Allocated | |--------------------- |----------:|----------:|----------:|----------:| | NicksFastest | 6.346 us | 0.1020 us | 0.0904 us | 16.36 KB | | InternetGuysQuestion | 15.097 us | 0.1417 us | 0.1325 us | 16.34 KB | As you can see Nick's suggestion still comes out over twice as fast. That's because hard casting is still faster than "as" or "is" casting. The type check in the fastest method is non-impactful to speed because nothing additional needs to happen to check the type except comparing some metadata already on hand. The type check is only 3 times slower than performing a comparison against an integer! That overhead is barely worth worrying about when used with any form of casting. However, your observations are right, the other methods are awful because they both have to actually perform a cast during the type check, and then a cast afterward. On top of the type check and hard casting being very fast, that's also why Nick's method is so much faster. [Later Edit] actually the overhead of the type check in the fastest method IS worth worrying about, but it won't get much faster. It takes up about a quarter of the overall execution time, which makes sense when comparing with Nick's results on hard casting. Any difference there is probably handling of the enumeration. If you don't need the safety checks or the flexibility of checking base types, I'd say it's well worth using, particularly in list casts.
@zhh1742 жыл бұрын
@@thehuggz-i9k yes you are right. Here the performance were not so obvious why it was happening like the OfType was so slow. My way also very slow. The performance difference here is for linq not for casting. My biggest problem with linq is it's not consistent with it's performance. Like in linq some methods are very optimized for array and list type collections. Like where and select. But OfType doesn't optimize anything for array or list. If you fist select then do where it also don't optimize that path. So if you want to get the actual result you need to use a custom IEnumerable which isn't optimized in any linq method
@brianm18642 жыл бұрын
Probably the most important thing I've learned from your videos... I benchmark things all the time to see what performs best. Thanks for the great videos!
@MechMK12 жыл бұрын
Benchmark what matters. I assure you, your casts don't. Whenever you have poor performance, it's down to one of these things: - Wrong algorithm - Repeated work - Poor IO - Waiting for mutexes I have never EVER been in a situation, where I said "Man, if only my casts were half a nanosecond faster".
@brianm18642 жыл бұрын
@@MechMK1 I wasn't actually referring to casts, but it's always good to know the most efficient ways to do anything, even if you aren't going to use the most efficient. But I've used benchmark for many things, including comparing algorithms, testing the performance of 3rd party libraries and database queries.
@boxacuva2 жыл бұрын
@@MechMK1 I think he confused Benchmark with analysing code.
@stacklysm2 жыл бұрын
The lesson I got from the video is: If possible, don't use the object type, this avoids unnecessary casts, nulls and overall headaches. Great video as always
@thehuggz-i9k2 жыл бұрын
I'm not surprised by these results. The rest of those have to either first perform a cast to check the type (as operator), or compare type COMPATABILITY (is operator). But the fastest solution makes sense as it just gets the current type, checks it against a single type and then hard casts it. Thanks Nick, this was instructive and insightful as always!
@xavhow2 жыл бұрын
Great vid, that's why I avoid object and type casting as much as possible... especially when dealing with large dataset! One question, in your example, isn't generic is better way of handling it in the context of enumerable... unless you really have to mix data types in one dataset (which is bad in my view). I know you are trying to compare casting speed.
@kevonboxill945510 ай бұрын
great great video! This is my 6th video searching for a good explanation on object type casting ,even tho it was about performance!!
@nooftube25412 жыл бұрын
you also can specify type in ToList() call instead of excplicit cast before calling ToList :)
@alphaanar26512 жыл бұрын
I use Unsafe.As if I am 101% sure about the type. I wonder if the results differ with value types instead. Also, when the object is null, the .GetType() throws, is operator returns false, as operator returns null. So if there might be nulls in a collection, I do not think GetType is a good idea.
@nickchapsas2 жыл бұрын
Unsafe will get a video fairly soon and I will mention it. I didn’t add it in the video because someone will see it, use it as gospel and then shoot themselves on the foot
@leandroteles78572 жыл бұрын
Unsafe.As is the fastest possible thing because it is a no-op at runtime, but as the name it says it's unsafe, and super easy to be used wrong. You have to be like 200% sure, and if you don't control the object that you're going to cast, you simply can't. Also Unsafe.As can be used not only on the lambda expression, but also on the enumerable itself. You can directly unsafe-cast List into List and it will work just fine, eliminating the linq calls, and therefore, with zero allocation. But it only works if the type of the enumerable is a reference type, trying to cast List into List will compile, but fail miserably at runtime, even if the list contains ints (because they are boxed).
@alphaanar26512 жыл бұрын
@@nickchapsas Ah, cool. And you are right about misusing this kind of class/methods.
@alphaanar26512 жыл бұрын
@@leandroteles7857 I am fully aware of how it works and how else it can be used :). You would have already thrown type safety outta window, right when you typed unsafe.
@leandroteles78572 жыл бұрын
@@nickchapsas A good way to use Unsafe is with generics, to cast the generic thing into its real type. I like to use the second variant of Unsafe.As and do something like: if (typeof(T) == typeof(int)) doSomethingWithInt(Unsafe.As(ref paramOfTypeT)); At runtime the branch will be eliminated and the cast will become a no-op. Even the core library uses this trick in some places.
@SecondFinale2 жыл бұрын
Love to see optimization enthusiasm that acknowledges readability.
@HeinzSeinz2 жыл бұрын
Fun fact: even if the as-cast returned null, Chuck Norris is able to read this null reference!
@TheGreatMiHeng2 жыл бұрын
Surely you mean Jon Skeet.
@vandermjr2 жыл бұрын
Under the Linq code: "return StaticObject.Person.OfType" and "return StaticObject.Person.Cast" we see the statement "Is": " if (obj is TResult result) return yeld result".
@CabbageYe2 жыл бұрын
Nick I heard there's a big performance boost when you mark classes as sealed, do you(or anyone else) know anything about that
@nickchapsas2 жыл бұрын
There is a video coming on this topic. It’s a bit deeper than that and I wanna touch on language design a bit too
@leandroteles78572 жыл бұрын
There is a boost because the runtime can optimize it into a simple type check, something equivalent to "obj.GetType() == typeof(SomeClass)". But if SomeClass is not sealed, then the runtime has to traverse all the class hierarchy of "obj" until it reaches "object", to ensure that it is not a "SomeClass". How big of a boost this will be, only benchmark will tell. If the type will be exactly "SomeClass" for like 90% of the time, the difference is likely to be negligible.
@GregUzelac2 жыл бұрын
I too was surprised. Wow. Thank you.
@benjamininkorea70162 жыл бұрын
Absolutely insane. I was proud to already know if (X is Y newname), but OfType() blew my mind because I've never encountered it before. I run a Blazor English website with many different types derived from a "Question" base class, and OfType() is going to make my code so much better organized and nicer! I currently use a Where() statement, but OfType() is just such a nice semantic.
@yobofunk56892 жыл бұрын
Use oftype a lot with datatable rows oftype
@yummybunny73512 жыл бұрын
What "!" mean in "default!" ? At 1:00
@jongeduard2 жыл бұрын
Using the null forgiving operator feels always a bit like kind of a bad practice to me, because you're suppressing a feature that you have enabled to help you. So I tend to always be very cautious with using it. But there are cases where you simply know it better than the compiler and if you are absolutely shure you can enable it. Keep in mind that by later changes to your code the null checks are still being suppressed, so when situations change to that the something can be actually null, you'll never know because you supressed it. But setting default nulls on properties can be a useful action in some cases.
@TheMAZZTer2 жыл бұрын
Should be noted that typeof(X) == x.GetType() only returns EXACT type matches. So a bad idea if you have or might have subclasses since they won't match. You can do typeof(X).IsAssignableFrom(x.GetType()) instead which will match more like you'd expect, but it is probably slower.
@jerryjeremy40382 жыл бұрын
Youre the best online teacher
@dimensionaltrader2 жыл бұрын
Exceptionally clear & helpful. Great stuff.
@gordonfreimann2 жыл бұрын
You post new videos faster than my compiler compiles my code.
@UrielZyx11 ай бұрын
Worth noting that the last strategy you introduced (GetType) is semantically different than all the others. If tomorrow someone introduces `class Worker : Person`, you won't get that worker in the resulting list...
@ronnyek42422 жыл бұрын
I think the comment about doing your own benchmarks is good advice in general. Thanks
@ovader90592 жыл бұрын
Reflection bit had me surprised!
@Z3rgatul2 жыл бұрын
LOL, why everyone is thinking GetType() is the slowest? GetType() just returns pointer to Type object. This object is already in the memory, nothing is getting allocated. No conditional statements inside. Just look at the object header and get pointer here. And comparing 2 Type objects is just comparing 2 pointers.
@nickchapsas2 жыл бұрын
Because most people associate GetType with reflection and to be fair, before the GetTypeInfo method was added, get type was heavier.
@haxi522 жыл бұрын
Thank you for your last comment about micro-optimizations. The cost of the next dev reading your code FAR outweighs the cost of writing ugly but "fast" code.
@0shii2 жыл бұрын
Interesting - Certainly did not expect that an OOTB linq method would be slower than just combining two other OOTB linq methods! Probably worth adding in a percentage of negative cases appropriate for your domain before making a final determination on the performance though...
@robertmckee92722 жыл бұрын
Wondering why you didn't benchmark People.Cast().ToList(); which I would suspect that would be the fastest. Of course that only works where all the elements are of Person type, but that is pretty common in the code bases I've worked on in the past.
@nickchapsas2 жыл бұрын
It’s not the fastest
@robertmckee92722 жыл бұрын
@@nickchapsas Interesting. I guess that shows the importance of benchmarking. I viewed the Cast method as internally doing a Select(x=>(T)x), but it does not, although I'm not sure why the SelectListIterator is so much faster than the CastIterator. Here are my results: | Method | Mean | Error | StdDev | Allocated | |---------------- |----------:|---------:|---------:|----------:| | OfType | 201.27 us | 4.006 us | 7.426 us | 256.41 KB | | Cast_As | 211.30 us | 2.018 us | 1.789 us | 256.44 KB | | Cast_Is | 216.41 us | 2.610 us | 2.314 us | 256.44 KB | | HardCast_As | 94.83 us | 0.740 us | 0.618 us | 256.47 KB | | HardCast_Is | 93.93 us | 0.901 us | 0.703 us | 256.47 KB | | HardCast_TypeOf | 85.21 us | 0.783 us | 0.733 us | 256.47 KB | | HardCast_Cast | 172.42 us | 2.153 us | 2.014 us | 256.41 KB | | HardCast_Select | 41.29 us | 0.299 us | 0.265 us | 78.25 KB | [Benchmark] public List HardCast_Cast() { return StaticObjects.People .Cast() .ToList(); } [Benchmark] public List HardCast_Select() { return StaticObjects.People .Select(x => (Person)x) .ToList(); }
@imensonspionrona21172 жыл бұрын
Great work!
@29Aios Жыл бұрын
9:31 Seems there is no check for x on null value at 81' line, however "is Person" or "as Person" has it inside. PS. It would be nice to see what the ASM code is generated for all the methods, not for me, but for the subscribers
@refactorear2 жыл бұрын
5:23 Hard to talk about clean code when having "object" in your code
@FlexNiko2 жыл бұрын
whats that "default!" on the property in the first class you created? google only shows me the default params for functions :D
@Faygris2 жыл бұрын
Great video! Thank you so much! So you've been using only LINQ for the enumerable types. But what's with _(List)StaticObjects.People_ and the equivalents with _as_ and _is_ ?
@leandroteles78572 жыл бұрын
This will throw because the real type of the object is List, not List, even though it contains only "Person"s. It would only have worked if the object was originally a List that got covariantly-casted into "IEnumerable".
@Faygris2 жыл бұрын
@@leandroteles7857 Of course! You're right
@GameDevASMR2 жыл бұрын
Interesting. I'm not surprised Harcast_TypeOf being the fastest, as you don't check for null cases when getting type, plus it doesn't support inheritance or interface cases.
@jongeduard2 жыл бұрын
If your goal is to really write the fastest code possible with collections or enumerables, you simply have to step back from LINQ at all. For the best performance, you will not write your code in the declarative, functional way of programming, but you'll just need to go old school in the imperative way, by using for or foreach loops and write your conditional statements and type casts inside that. LINQ is built to be super handy, to drastically improve your productivity, and still have reasonable performance. But it will and can never really outwin the old way.
@kirillhorn31862 жыл бұрын
Thank you very much for your videos! I have learnt a lot!!!
@owns32 жыл бұрын
I'd be curious if the type was sealed if there's a "big" difference
@the-niker2 жыл бұрын
Thing is "as", "is" and hard cast will all cast a derived class to a base class while == typeof(baseclass) would fail. So you are skipping inheritance check there which may not be what you want.
@MrDaedra882 жыл бұрын
It will because that example uses hard casting in Select
@the-niker2 жыл бұрын
@@MrDaedra88 nope, the Where filter would ignore inheritance and the list would be empty if you're casting to a base class.
@pkonstantellos2 жыл бұрын
As mentioned in the video, one should not optimize code for such a small performance gain. The main reason is that the language itself is changing over time. Thus, "slower" operations in the current version may be "faster" in a newer version.
@29Aios Жыл бұрын
Sure, and you are right. PS. In my current project, I'm receiving 700'000 Kafka events per second, had to replace properties to fields, removed all async methods, and replaced throwing exceptions to return values using Result, it worked, but still have many service instances.
@lucwidmer62942 жыл бұрын
How about doing an episode between ToList() and ToArray(). Everybody always seems to use ToList() but I would really expect ToArray() to be more efficient im many scenarios where you don't change the collection anymore after it's created. I really enjoy your work.
@nickchapsas2 жыл бұрын
Be sure that a video covering the topic is coming soon
@denisosipenko74132 жыл бұрын
Great! Thanks a lot, it's a really usefull matherial.
@ПрокопеняАлександр2 жыл бұрын
Братан, хорош, давай, давай, вперёд! Контент в кайф, можно ещё? Вообще красавчик! Можно вот этого вот почаще?
@anon-qn5fc2 жыл бұрын
nice c# casting video, I enjoy
@Muthukumaransubbiah2 жыл бұрын
Thank you for this! Super cool video! A+++
@andytroo2 жыл бұрын
would checking for interface implementation change these values? what about parent/child types ; what if Nick isn't a Person, but an Educator - some of these checks succeed/fail different ways?
@ahmedhafez86982 жыл бұрын
WORKS 100%
@rick25912 жыл бұрын
What approach can I take when I do not know the objects that are going to be in a class. Is it Okay to use reflection in that case to get the names of the objects
@j1shin2 жыл бұрын
But in case of enumerables, the question now is why is OfType slower than HardCast_Is and why is your UI then hinting towards changing HardCast_Is to OfType? Did I miss something?
@jeronimojunior2 жыл бұрын
1:51 GUARANTEED! 😆😆😆
@PauloMorgado2 жыл бұрын
How does that all play when implicit and explicit type cast operators are defined?
@br3nto2 жыл бұрын
Is there a reason they don’t they update the implementation of OfType() to one of those faster methods?
@slipoch66352 жыл бұрын
re: the collection, can you do this without a select statement? As I always find if I include select (.net framework primarily still) it ends up blowing out ram and cpu time.
@tanglesites2 жыл бұрын
Nick I know his off topic, but I am having a hard time wrapping my head around the Onion Architecture, and how to organize my project. I have researched it online and found different opinions. The goal is to have the flow of control start from the outside at the presentation layer and communication only goes inward and what is inside does not reference what is outside, Presentation -> Service -> Domain ... But the presentation and the Infrastructure are on the same tier ?? I am confused. Is this the same as the N-Layered architecture.. is this a logical organization?? If you can answer or make a video about this... This just don't teach this stuff in school.
@nguyenquangchienk17hcm882 жыл бұрын
Hey Nick, can you share your Rider's theme? Mine looks a bit like Java and I love your version more.
@JonathanPeel2 жыл бұрын
I appreciate this video. I am going to remember these results for when I might need it. I will probably stick with OfType for most queries, and take the safety over speed.
@brtk72 жыл бұрын
You’ll never gonna needed in real life applications, better spend time on defining low coupled contexts with high cohesion than thinking about these benchmarks but somethimes if you’re doing a lot of heavy casting and cannot avoid it then yes better keep that in mind and benchmark your code as well.
@evancombs51592 жыл бұрын
@@brtk7 That is not true, I was just dealing with this last week. When you deal with big data a slight performance gain can save you money.
@brtk72 жыл бұрын
@@evancombs5159 If you’re doing something performant critical then indeed diving into IL is reasonable. There is many cases like method lengths that disable method inlining etc. it’s hard but if know .net well it’s doable. Interesting thing I found „The IL is only describing what happens in terms of some abstract virtual machine. It may (or may not!) have no resemblance to the machine code that comes out the other end. (An easy way to view the generated code is to break on the start of the method and look at the code in the Disassembly debugger window. Make sure you *disable* "suppress JIT optimization on module load" to get the optimized version.)”
@ricardopieper112 жыл бұрын
For GetType() == typeof(Person) I think this is almost a simple integer comparison of pointers? .GetType() should be super fast, I think it's just a reference/pointer of a Type object already loaded in memory, and .Equals on Type is just ReferenceEquals of objects... looks like a simple pointer equals check.
@rade60632 жыл бұрын
Hey Nick, great video. I have a question regarding unit test and integration test courses. Do you plan on adding videos for testing endpoints (from fast endpoints library) and should those be integration or unit tests? I tried unit testing them but since HandleAsync doesn't return anything I cant really test them with unit tests or am I missing something.
@nickchapsas2 жыл бұрын
I’d have integration tests on that level generally speaking
@taciolitke2 жыл бұрын
Regarding methods that doesn't return anything, using mock in your dependencies you can test the number of times a dependency method has been called or threw an exception in your dependencies as logger, services, repositories or handlers. Maybe it might make sense for your context
@antonmartyniuk2 жыл бұрын
Nice video!
@Havie2 жыл бұрын
U r the man
@saurabhchauhan2322 жыл бұрын
Can you make one video on salting and hasing password to store in db?
@williamliu89852 жыл бұрын
Just curious why not msft just make them the same underlying implementation if most of these commands are doing the same thing...
@evancombs51592 жыл бұрын
I can understand why the more manual ways may not want to be optimized. There might be some implementation detail that prevents it. What doesn't make sense is why the more declarative method does not use the most efficient casting.
@parkercrofts62102 жыл бұрын
ice its actually working, im suprised
@osman34042 жыл бұрын
I go with whatever reshaper refactoring suggests
@pw.702 жыл бұрын
🌬🧠 = Mind_Blown...
@freddyflares27572 жыл бұрын
I was waiting for the dynamic keyword to make an appearance and the double dispatch pattern. protected override void OnDispatch(object thing) => OnSomething((dynamic)thing); void OnSomething(Person person) { Console.WriteLine(person.Name); } It's not _that_ slow.
@andrewmaksymiuk9862 жыл бұрын
You didn't mention the switch pattern casting but it's probably just syntactic sugar for one of these anyways
@nickchapsas2 жыл бұрын
Yeah switch is just the same as match casting
@Technology_555552 жыл бұрын
What are the complete steps to create a PayPal adder money program?
@efrenb52 жыл бұрын
Old school > syntactic sugar. 'Nough said. LOL
@rngesus80572 жыл бұрын
i purely use .OfType to filter out nulls lol
@qm3ster2 жыл бұрын
The simplest/shortest/most specialized standard library method should also be the fastest. This is an enormous failure on the part of the standard library/compiler/runtime.
@boxacuva2 жыл бұрын
I think pretty much all of them are valid ways and context dependent to cast. Maybe there is 1 that should be avoided.
@EverRusting2 жыл бұрын
TL;DW: JetBrains insults you with your every key press
@syrymjoli2 жыл бұрын
👍
@ibrahimhussain32482 жыл бұрын
What the heck is default! ?
@phizc2 жыл бұрын
The exclamation point is the null forgiving operator. It suppresses the warning that the property could be null. It's basically setting the field to null but tells the compiler "I know what I'm doing".
@ibrahimhussain32482 жыл бұрын
@@phizc thanks :)
@davidwright57192 жыл бұрын
Don’t use Linq. Seriously. It just obscures and genericizes the logic that is used for collection processing. Actually think about and make your collection processing logic explicit.
@alluseri2 жыл бұрын
unchecked casts are supreme and you don't even know about them smh
@Waffle4569 Жыл бұрын
Unsafe.As is by far the fastest.
@Fikusiklol2 жыл бұрын
Hey, Nick! New comer here :) Tried this and resuted even faster. public List Cast_Span() { var list = new List(); foreach (var item in CollectionsMarshal.AsSpan(StaticObjects.People)) { if (item is Person person) { list.Add(person); } } return list; }
@adamponka64042 жыл бұрын
Just as I thought adding `sealed` keyword to make the `is` operator work like comparing to specific type, speeds up the hard casting by 15%. Here are my benchmarks with and without the `sealed` keyword: | Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | |------------------ |---------:|--------:|--------:|--------:|--------:|--------:|----------:| | HardCast | 182.6 us | 3.59 us | 5.26 us | 83.0078 | 82.5195 | 41.5039 | 256.47 KB | | CompareType | 151.4 us | 1.66 us | 1.47 us | 83.0078 | 82.5195 | 41.5039 | 256.47 KB | | HardCastSealed | 153.5 us | 1.46 us | 1.22 us | 83.0078 | 82.5195 | 41.5039 | 256.47 KB | | CompareTypeSealed | 147.5 us | 2.85 us | 4.17 us | 83.0078 | 82.5195 | 41.5039 | 256.47 KB |
@artemivanov21412 жыл бұрын
Why you use default! in "public string FullName { get; set; } = default!;" ??? @nickchapsas
@nickchapsas2 жыл бұрын
Because this is a not null value so I default it as such
@artemivanov21412 жыл бұрын
@@nickchapsas thanks! I'm not used to non-nullable types yet (
@HuntingKingYT2 жыл бұрын
But what with sealed classes? How do *they* perform?