When LINQ Makes You Write Worse .NET Code

Sdílet
Vložit
  • čas přidán 13. 09. 2024
  • The first 200 get 30% off our new Git and GitHub Actions courses on Dometrain with code GIT30: dometrain.com/...
    Subscribe to my weekly newsletter: nickchapsas.com
    Become a Patreon and get special perks: / nickchapsas
    Hello, everybody. I'm Nick, and in this video, I will talk about LINQ in .NET and C# and how it has made us write worse code because we stopped being critical about how it works.
    Workshops: bit.ly/nickwor...
    Don't forget to comment, like and subscribe :)
    Social Media:
    Follow me on GitHub: github.com/Elf...
    Follow me on Twitter: / nickchapsas
    Connect on LinkedIn: / nick-chapsas
    Keep coding merch: keepcoding.shop
    #csharp #dotnet

Komentáře • 239

  • @nickchapsas
    @nickchapsas  Před 18 dny +20

    If you think this video is about performance then you didn’t actually watch it or you lost the plot

    • @paxcoder
      @paxcoder Před 18 dny +5

      That's a huge difference in performance. Why is Enumberable.Last so slow? It's not the checks, is it? And it's not iterating over the whole collection: Eventually TryGetLastNonIterator gets called, which indexes [count -1] just like we would. Is it type matching that it has to do to determine it can do that? If so, why don't we a more specific IList.Last, so that it gets called instead of the Enumerable.Last that forgets the actual type?

    • @TazG2000
      @TazG2000 Před 18 dny +40

      You're saying it's about intent, but Last() absolutely perfectly indicates the intent of getting the last item, and is consistent with all other uses of First/Last throughout the codebase. If you have the opinion that indexing communicates intent better than First/Last, it isn't one we all agree with, and nothing in the video supports that opinion. The only reasons you gave that it was "worse" code were driven by optimization. So it's only natural that this comes off as a performance focused video.

    • @RobinHood70
      @RobinHood70 Před 18 dny +2

      Idea for a future video: places where you SHOULD use LINQ if you're new to it or don't tend to use it as much as you should.

    • @petervo224
      @petervo224 Před 18 dny +11

      @@nickchapsas I don't care about performance, intent, nor plot. I just hope I don't have to deal with any idiot who would show me this video as an excuse for not using LINQ (especially when it should be). 😑

    • @noguai1707
      @noguai1707 Před 18 dny

      I fully agree with @TazG2000 and I personally even hate this "^1" syntax and think how it is defined was a bad design decision by the dotnet team. Why is it that when counting (indexing) from the beginning we start with 0 as so many programming languages do (not the important part), but when counting from the end we start with 1. Sure, something one can get used to, but this just feels very inconsistent to me and calls for off-by-one errors. As we typically do not just want the last value of a collection but want to do something with it, I find it much more descriptive to have the method that says what I want in plain words.
      If not about performance, the whole video to me is even a little contradictionary, when after explaining why the benchmark is bad you start the topic by saying: "When you have something like this person list over here and you say "I want the last item" you might be tempted to just say - you know - I want the last item, so ".Last". And to be honest there's nothing necessarily wrong with this approach."
      Doesn't this statement say in a very obvious way, that this would be, from a readability perspective, a perfectly reasonable thing to do? In the context of the other videos showing off improvements to LINQ like the Min/Max methods, i even find it problematic to say: Use these LINQ methods because they are nice, fancy and fast, but never these other ones because of some debatable reason. I'd tend to lean on: If in some context I'm already using LINQ or my code/logic is rather high level or needs to be as descriptive as it can, sure why not use the Last method as long as it's not a performance issue. And in the same way: If I'm already down in the nitty gritty details or some hot path, yeah use the more appropriate "low level" operations. In other words: I'd say it depends (i know, i know...), as poor of an explanation that may be, on the abstraction level of the code around it.
      And if I really really badly wanted that high level LINQ call but actually had performance problems because of all it's additional checks and indirection because of the interface, for something as ubiquitous as LINQ, I'd even think about adding my own **compatible** extension for the more concrete type I'm working on that may then skip unnecessary operations and chooses the optimal code path for this type right away.
      What I think that this discussion highlights, is that extension methods, as cool as they are, tend to obfuscate a little what methods are actually defined on a type and what methods come from random extensions via some random interface, which because of that might not be as optimized as possible or just, because of their nature, have to handle more cases.

  • @SuperLabeled
    @SuperLabeled Před 18 dny +239

    Readable code > 8 nanoseconds

    • @nickchapsas
      @nickchapsas  Před 18 dny +23

      Exactly 😄

    • @stoino1848
      @stoino1848 Před 18 dny +5

      premature optimization... ;) however, I think it is good to know. If you have resource constraints or this run for many many parallel request...
      Always measure your hot path first 👾

    • @cranter7289
      @cranter7289 Před 18 dny +12

      what is not readable about the indexer solution?
      when you see x++ instead of x = x +1 you will also complain? 🤔

    • @neonmidnight6264
      @neonmidnight6264 Před 18 dny +5

      Index syntax is fine. If list[^1] is something that makes you think twice, it is a skill issue. @stoino1848 same applies to this statement, you should really stop saying it without understanding wider context and implications of what Donald Knuth meant. This case is the example of where less code that is simpler is a performance win. You must always take this kind of change and if I were to see this reply from you on code review I would sternly scold you.

    • @prman9984
      @prman9984 Před 18 dny

      @@cranter7289 I just looked at some Javascript code with a young guy and he had no idea what i++ was doing, whereas he could read everything else. He's self-taught but had to ask me what i++ was.

  • @timlong7289
    @timlong7289 Před 18 dny +43

    I always look at the alternatives and go with what is more easy to read and understand. On that basis, I will often use the LINQ methods even though they are potentially less performant. In my line of work, I'm nearly always IO-bound therefore performance is mostly irrelevant. Therefore, I tend to always advocate for readability, simplicity and clean code over all else.

    • @prman9984
      @prman9984 Před 18 dny +18

      As a retired Software Architect, I can count on one hand the times that this sort of performance optimization actually made a difference in the real world. If you need "Last" to be that fast, then you need to refactor your code and make it "First".

  • @ethanr0x
    @ethanr0x Před 18 dny +22

    or write an extension method on List that does this with the name Last?

    • @alfflasymphonyx
      @alfflasymphonyx Před 18 dny

      Exactly!

    • @b33j4y
      @b33j4y Před 18 dny +1

      or better yet ICollection

    • @cruz1ale
      @cruz1ale Před 18 dny

      @@b33j4y Can't apply indexing with [] to ICollection

    • @ethanr0x
      @ethanr0x Před 18 dny +3

      @@b33j4y maybe IList tbh cause of the indexer which we need.

    • @b33j4y
      @b33j4y Před 18 dny

      @@ethanr0x true - i assumed using Count instead of [^1]

  • @fusedqyou
    @fusedqyou Před 18 dny +15

    It's important to mention that LINQ isn't supposed to be better performance wise. It tries incredibly hard to be good at what it does, and it tries to support all possible scenario's when doing this. When you develop your application you often prefer to spend the least amount of time doing it to make sure it gets done in time, and you want to make sure it stays readable. With LINQ it remains very clear what actions are happening, and I would pick this over its negligible performance loss any day. That said, when performance is important the topic is obviously different, but I'd argue you can often abstract "unreadable" code away into something readable elsewhere.

    • @Zullfix
      @Zullfix Před 18 dny +1

      In cases like Sum() on array-based collections (without a selector), LINQ is actually faster due to vectorization. For just about every other case though, you are correct.

    • @evancombs5159
      @evancombs5159 Před 18 dny

      The loss in speed is usually all of these checks it is doing too make sure it does things correctly for all possible scenarios. So I'm most cases when performance is a requirement you should be able to write a dedicated method that performs to the requirement without sacrificing readability.

    • @Darknimbus
      @Darknimbus Před 10 dny

      There's a nice key word we like to use here for this: "Premature Optimization".
      Yes, you will get more performance but that can be fixed when you see the performance is an issue not before. You are mostly wasting a lot of time and making code a lot less maintainable/readable for a negligible gain. And if there is an issue in performance I can bet you anything it probably has nothing to do with this line of code.
      This type of optimization is probably only needed on machine level code but then why use C#?

  • @markovcd
    @markovcd Před 18 dny +72

    Just write readable code, don't do premature optimizations.

    • @keyser456
      @keyser456 Před 18 dny +7

      The application (as in the context it's being used) matters. If it's a long-running process with a large # of persistent connections all vying for CPU time and "higher frame rates" using performant libraries and techniques is a must. There's no such thing as "premature optimizations" in that context. In a more traditional web server scenario where load isn't too demanding or especially in SPA scenarios where the client/browser is running the code, then you can make the case for using slower libraries for the sake of readability and ease of use. Don't get stuck in the one-size-fits-all mentality.

    • @timlong7289
      @timlong7289 Před 18 dny +8

      @@keyser456 I somewhat agree with that, but there is always such a thing as premature optimisation. If you haven't measured it, it's premature.

    • @Zullfix
      @Zullfix Před 18 dny +2

      ​@@timlong7289Initially I disagreed with your statement, but the more I thought about it, the more I found that I more or less agree with it.

    • @enricoroselino7557
      @enricoroselino7557 Před 18 dny

      if its your own code, no one cares.. if its not it will be refactoring giant later

  • @adambickford8720
    @adambickford8720 Před 18 dny +68

    Completely disagree. Almost every bug I encounter has something to do with 'bookkeeping' code like tracking indexes, boolean flags, etc. `last()` perfectly communicates the intent of the developer and is resistant to things like assumptions about the list content.
    Unless this is in a *very* hot loop I'd actively advocate *against* this advice.

    • @SirBenJamin_
      @SirBenJamin_ Před 18 dny +10

      How is using .Count or .Length more buggy than Last()? ...

    • @timlong7289
      @timlong7289 Před 18 dny +20

      @@SirBenJamin_ I believe he is advocating for more declarative code over imperative code-tell me WHAT to do, not HOW to do it. Tell me what you MEAN, not the steps required to get there.

    • @foolmoron
      @foolmoron Před 18 dny

      The amount of code I've had to fix because devs didn't realize Last() throws on empty is way too high
      Your argument applies to LastOrDefault but definitely not to Last()

    • @smathlax
      @smathlax Před 18 dny +7

      Idk, people[^1] is short and to the point and very readable IMO.

    • @adambickford8720
      @adambickford8720 Před 18 dny

      @@timlong7289 Exactly this.
      Unless there is a measurable *need* then solve the problem you actually have. You are likely trading brittleness for performance you don't actually need (or can even measure in a real app).

  • @themiwi
    @themiwi Před 17 dny +4

    The fact that MS did not overload Linq methods for specific types/interfaces is the real issue here. You could have uniform syntax with no performance impact.

    • @raphaelschmitz4416
      @raphaelschmitz4416 Před 16 dny

      Well... if it's so performance critical that you can't allow yourself those 8 nanoseconds for the LINQ method... _can you even allow yourself a fancy method call at all_ ? You should probably be working with arrays and access them directly via index number. Maybe C# isn't even the right language anymore.

    • @finickyflame
      @finickyflame Před 9 dny

      I'm still surprised that they even preferred to create a code generator for logging message instead of providing generic extension methods to fix the boxing issue on the arguments. Adding those kinds of extension methods just improves the performance of existing code so easily.

  • @andersborum9267
    @andersborum9267 Před 18 dny +2

    Worth considering here is that the .Last() LINQ operator is likely going to be subject to significant optimization, i.e. if the source is of type IList or similar that supports deterministic indexing, allowing for a rewrite to a similar native implementation as was presented here. Regardless, don't do premature optimizations, and always favor readability. If you're a game developer, you won't be using LINQ anyway, except for edge cases.

    • @raphaelschmitz4416
      @raphaelschmitz4416 Před 16 dny

      As a game developer, I actually do it the other way around; use LINQ except for edge cases. Haven't run into those yet, actually.
      Like... 60 times per second, LINQ-filter 1000 objects to update them - that's only 60 LINQ calls per second.
      If every one of those objects itself wants to call .Last(), it's 60 000 - still not THAT big, and actually sounds like you should be doing that a different way anyway.

  • @FrancoisBothaZA
    @FrancoisBothaZA Před 18 dny +6

    I honestly thought that each IEnumerable subclass would have its own, optimised implementation of the LINQ methods and that polymorphism would have the correct one executed.

    • @Briezar
      @Briezar Před 18 dny +1

      well it's called LINQ extension methods for a reason. It usually has cast checks for collections for optimisations. Count() for example would run differently for List and IEnumerable.

    • @prman9984
      @prman9984 Před 18 dny

      It does. That's why it's 8 ns and not 200 ms.

    • @IllidanS4
      @IllidanS4 Před 18 dny

      That's pretty much what Rust does, but not here ‒ there are so many methods that it would be horrible to have to implement them all, and they may not even apply in some (most) of the cases ‒ like what is Last() of an infinite sequence? Sure, there are default interface methods on .NET Core now, but it was not the case when LINQ was introduced, and it is still not the case on .NET Framework.
      Instead it does the reasonable thing ‒ it uses specialized interfaces as much as possible, but it can always fall back to the "by definition" case. Basically every operation is expressed in terms of simpler operations that are directly supported by those interfaces.
      However even this can bite you! Once I implemented Count on a custom IList using LINQ Count() and found myself in a nasty recursion since Count() goes for IList.Count when it can!

  • @ryankueter9488
    @ryankueter9488 Před 14 dny

    Nick, benchmarkers such as yourself provide a very needed service to the developer community. Benchmarking is an exercise that most developers neglect to do or don't have time to do. So, thanks for showing that there is more to the story than meets the eyes.

  • @logank.70
    @logank.70 Před 18 dny +7

    I go to the "use LINQ unless you can't" school of writing code. If I can't use LINQ for a particular part of the software I'm working on then there's a comment explaining why. I agree, if you know the type you are working with you can go a different route that will get you there faster. Why go through all those hoops and method call after method call if you don't have to? However, I do enjoy the consistency of just using LINQ when I'm working with collections. As an added bonus, in my opinion, it's easier when you are working on larger teams to keep things consistent. It's easier to remember "use LINQ unless you can't" then it is to remember all the nuance. For experienced developers it isn't too bad but a rule like "use LINQ unless you can't" is to protect the codebase from the non-experienced developers.

    • @RobinHood70
      @RobinHood70 Před 18 dny +1

      I'm on the flip side. I don't use LINQ without a *really* good reason, and this video shows exactly why. In most code bases, laggy code like this adds up surprisingly quickly to deliver an app that feels sluggish. Not using LINQ goes double for most public methods, since you can't know what the person's use case will be. That all said, though, I'll be the first to say that best practice probably lies somewhere in the middle and that I should probably use LINQ more than I sometimes do.

  • @Robert-yw5ms
    @Robert-yw5ms Před 18 dny +5

    Usually visual studio is kind enough to tell me when I'm mising a LINQ method (mostly .Any()) and I just press alt+enter to fix it.

    • @Xastor994
      @Xastor994 Před 18 dny

      I haven't seen it make suggestions for index access like this (though it might just not have come up for me), but it definitely should. Though I am confused if it knows to make that suggestion why wouldn't it make that optimization in the compiler..

  • @DynamicalisBlue
    @DynamicalisBlue Před 18 dny +27

    I don’t get why the .NET compiler can’t auto-convert basic LINQ expressions to expected code.

    • @adambickford8720
      @adambickford8720 Před 18 dny +4

      It depends on the API. The 'higher' up you are, the more general/less efficient. For example, `IEnumerable` could be infinite so can't depend on things like Count. A `List` does have a count so could use that optimization, but a count implies other restrictions.
      They can and do make optimizations like this between releases

    • @prman9984
      @prman9984 Před 18 dny +5

      It does. As the look into Last showed with it's "if IList then .Count-1" code shows.

    • @bluecup25
      @bluecup25 Před 18 dny

      @@prman9984 That's not the compiler, that's the runtime.

    • @milinmt
      @milinmt Před 18 dny

      it's explain in the video. LINQ expressions have some usefull guards. here we can use [^1] because we know that it's a list.

    • @buriedstpatrick2294
      @buriedstpatrick2294 Před 18 dny +3

      @@milinmt It really isn't really explained, exactly. The count safe-guard makes sense, but that isn't really what makes the difference performance wise as the benchmarks very clearly show. It's the pointless type checking done at runtime that adds the real overhead. You could make an overload extension method on List that performs just as well as the index-based lookup without the additional type checking.

  • @WaldenL
    @WaldenL Před 18 dny +7

    Performance is important, but often (not always!) second to maintenance. Remember, someone may have to understand this line of code, at 3am, on vacation, from the beach, after drinking four margaritas - and that someone may be you!

    • @martinprohn2433
      @martinprohn2433 Před 15 dny

      I still hope, that I am the someone drinking Margaritas at 3 am on vacation.

  • @browny99
    @browny99 Před 18 dny +5

    This is fine if your code never changes, but what if you go from an API call to an EF Core database and want to reuse code, all the index math now has to be changed and for what? Less readable and 10ns faster code? Nah

  • @BonBaisers
    @BonBaisers Před 18 dny +11

    Most of the time those performance issues are not relevant. Few nanoseconds won't do much during a 50ms RTT with a any service. But lately, I am working on a high performance data pipeline and man, Linq + GC can be performance killers in this case.

    • @keyser456
      @keyser456 Před 18 dny +3

      Yep. Gaming or any experience with persistent connections and people vying for slices of CPU time and higher frame rates, performance can't be an afterthought there either.

    • @prman9984
      @prman9984 Před 18 dny +4

      @@keyser456 Exactly. But in 99.9% of business software it just doesn't matter.

  • @jimv1983
    @jimv1983 Před 18 dny +1

    I prefer the syntax and readability of the Last() method. Sure it might be slower but we're talking abot nanoseconds. You could call that Last() method 125,000 times and it would still only take ONE MILLISECOND.

  • @vimalvaira
    @vimalvaira Před 10 dny

    i’m more surprised that more people did not dislike the video, your example is quite simple, but that is not how Realiti works, unless you’re working in a very specific project that requires this kind of optimisation rather not use c#.
    I won’t be surprised if I see someone trying to fit extra code in that if check for if list has count. This is an invitation for poor code that will waste more time in maintenance if ever changed.

  • @AronK
    @AronK Před 17 dny

    Agree that there is some abuse in the use of IEnumerable extensions in unnecessary scenarios, harming performance and readability.
    I like to use for manipulating ienumerable like: maping, filtering, grouping, aggregating, etc. I.e: whenever you know you have to loop over the enumerable at least one time

  • @Deathflame82
    @Deathflame82 Před 18 dny

    Last() as first option.
    [^1] if I also need others items from the end (ie [^2], ..) for symmetry and consistency.
    I also added, in some cases, custom ICollectionExtensions/IListExtensions witht the equivalent IEnumerable extensions methos , but opimized (ie Any() is Count > 0, etc)

  • @UgrevsBoots
    @UgrevsBoots Před 12 dny

    Always good to have tools in your toolbox. It's a bit of premature optimization, but nothing harmful to learn new syntax.
    8ns is not noticeable for 95% of the apps out there. It's when your data sizes are incredibly large where optimization really counts. IMO.
    Not saying I wouldn't use this, but if i wanted to use indexes...ya know? I would just use indexes. [^1] is ugly no matter how readable. Zero reason why you can't hide this in an extension method though.

  • @johnnyvdoremalen
    @johnnyvdoremalen Před 5 dny

    I was expecting there to also be a transformation before invoking Last() 😂

  • @jasoncox7244
    @jasoncox7244 Před 18 dny

    There are some other LINQ methods that I'm curious about the guts behind. The By(predicate) methods. Like, ExceptBy(...) or DistinctBy(...) I find my self feeling extra lazy when I use them, but they're so nice. It would be cool to see how they actually perform vs. standard algorithms.

  • @bslushynskyi
    @bslushynskyi Před 17 dny

    I use LINQ only if there is no indexer in the underlying type. I use LINQ mostly for doing fancy with collections using Select(), Count with predicate, Where, etc.

  • @matthewsheeran
    @matthewsheeran Před 18 dny +1

    I liken Linq to Reflection, its not of course, but it is quite heavy, just like it, especially First, Last, and Any as Nick shows. BTW: The performance difference doesn't matter in foreground GUI processing, BUT does almost everywhere else in the background!

    • @prman9984
      @prman9984 Před 18 dny +1

      Not true. You will almost always be waiting for some kind or networking or disk which is 5 orders of magnitude higher wait times, making 8 ns completely irrelevant when you are waiting 200±20 ms anyway. Nobody will notice or care and it will be immeasurable.

  • @MichaelBattaglia
    @MichaelBattaglia Před 17 dny

    Just implement a method that returns the last item via the index operator and call that instead of Last

  • @RandallEike
    @RandallEike Před 18 dny +2

    Early optimization is the root of all evil.

    • @nickchapsas
      @nickchapsas  Před 18 dny

      Not about optimisation

    • @RandallEike
      @RandallEike Před 18 dny +4

      @@nickchapsas I watched the video twice. A big focus of the reasoning was about making it "faster." A benchmark was touted and discussed in detail. Never was it stated that even if performance was equal, it is better to not use LINQ. Your example at the end replaced one line of LINQ with multiple lines of custom code; which if performance optimization is not a concern is a step back in my view.
      With that said, if I know that my Enumerable is a List or Array, I promise to use use the indexer rather than First(), Last(), or Any() moving forward :)

  • @padonker
    @padonker Před 7 dny

    How about adding your own extension methods in your project to catch these things?

  • @dusrdev
    @dusrdev Před 18 dny

    This is a good general rule but sometimes Linq will be better for even simple things, I.E Sum or Max that use SIMD will be much faster than regular loop over a list with indexer... Maybe just take the 30 seconds it takes to follow the execution path of Linq using F12, after which you just remember for the next times what to use and where.

  • @Bliss467
    @Bliss467 Před 18 dny +1

    Why didn’t they simply override Last for Lists?

  • @Saleca
    @Saleca Před 18 dny +1

    Would you make a video explaining when one would use Last() and Any()?(And other similar methods that arent usually recommended) Since for Last() you should be sure you have items on the collection and the compiler always says " prefer count over Any() " for non ienumerable

  • @kairuilow2226
    @kairuilow2226 Před 18 dny

    Just recently implemented an extension method for LastOrDefault with predicate to use index and start from the last item instead after knowing the default implementation uses enumerator to enumerates from the start. My project in . NET Framework 4.7 btw. Using the extension method keeps the readability I think.

  • @Ilix42
    @Ilix42 Před 18 dny

    The biggest problem I’ve seen with LINQ is that people will suggest it to folks who are first learning, before the people even know how to accomplish the task without LINQ.
    I’m glad I haven’t taken on code with terrible LINQ.

  • @luciannaie
    @luciannaie Před 16 dny

    they should revisit their old implementations to improve their speed. this kind of knowledge should not be required.

  • @miguelgremy
    @miguelgremy Před 15 dny

    Quick question tho, I like to write several function with IEnumerable insteadofList or whaterver so I can get any types in here. Does that really affect performance compared to stricly using List or something else ?

  • @guenolelacroix6434
    @guenolelacroix6434 Před 18 dny

    Thanks for this vidéo. Have many difficults with indexer, but i will learn them. 🙂

    • @prman9984
      @prman9984 Před 18 dny

      "Have many difficults with indexer, but i will learn them." Which is the prime example as to why Linq should be used instead.

  • @TheTigerus
    @TheTigerus Před 17 dny +1

    If you need to make your code more performant, 8ns is probably not your target.

  • @rcranjos
    @rcranjos Před 17 dny

    Linq overuse lead to lack of knowledge about collections use cases.

  • @xnetc9
    @xnetc9 Před 15 dny

    Boss likes Linq and requires it as best practice.

  • @ThugLifeModafocah
    @ThugLifeModafocah Před 18 dny +2

    I just come back to it if this shows up as a performance bottleneck. Otherwise, I will not optimize ealier.

    • @andreaspetersen2662
      @andreaspetersen2662 Před 17 dny

      But if you write it optimized to begin with you wont need to come back to it at all

  • @gani3813
    @gani3813 Před 18 dny

    Hi Nick. I completed a course on Dometrain. But my name on the certificate is shown as Google User. It didn't get updated even after I fixed my name in the profile. I emailed to dometrain support but no response so far. Could you please help with that issue?

  • @twiksify
    @twiksify Před 18 dny

    However, this only works as long as you are using a concrete collection type. Won't work with list.Where(predicate) instead you would have to rewrite the whole LINQ expression.

  • @bytejuggler
    @bytejuggler Před 9 dny

    It's kind of stupid that .Last() is so much slower than than [^1]. Kind of a violation of of Principle of least Surprise. But hey ho. (One could, IMHO, somewhat reasonably expect there to be optimization such that the code compiles down to equivalently fast execution. Obviously not the case sadly.)

  • @alfflasymphonyx
    @alfflasymphonyx Před 18 dny

    I think they should update the LINQ to check for the source to be either List or Array first in their checks.

    • @prman9984
      @prman9984 Před 18 dny +1

      They do. As you can see when Nick went into it. Any checks forArray/IList/ICollection and uses Length/Count also. But doing that type-check takes 8 ns.

  • @yytflo1058
    @yytflo1058 Před 18 dny

    Ist it only with all first and last methods, or is it also with .Select() .ForEach() .Were() and so on?

  • @drleandrocorrea
    @drleandrocorrea Před 18 dny

    What are the use cases in which LINQ's perfomance matters? I've seen plenty of SQL queries killing performance but never an issue with LINQ itself.

    • @nickchapsas
      @nickchapsas  Před 18 dny

      It depends on what type of code you write. For most code performance doesn’t matter but intent does

  • @T___Brown
    @T___Brown Před 18 dny

    This is going to lead to a "CountableEnabled" element in the project now. lol. Where the array is known to have at least one item. var x# = new List(1,2,3); 🤣🤣🤣🤣

  • @buriedstpatrick2294
    @buriedstpatrick2294 Před 18 dny +1

    I have never understood why it works this way. Why doesn't LINQ just have a specific .Last() extension override method for the List type that does the quick index fetch under-the-hood? Like yeah, I understand that LINQ operates on very generic IEnumerables and to make it chainable you need to do it generically. But .Last() isn't exactly something you can chain. So if you just have a List and the compiler knows it's a List, why not just use the list[^1] construction internally instead of the generic one?

    • @Briezar
      @Briezar Před 18 dny

      what would the compiler choose when there's multiple .Last() overload for List, IList, IEnumerable? You can write your own .Last(List) and and use extension .AsEnumerable() to switch to LINQ, but if you're working with complex queries, most of the time List would be returned as IEnumerable anyway.

    • @buriedstpatrick2294
      @buriedstpatrick2294 Před 14 dny

      @@Briezar Last() returns the last element of the collection, the return type is known at compile time. It's the same for all collection types, IEnumerable is not at all relevant in this particular discussion except in the sense that List would have to overload it.
      One problematic aspect I could see here is the fact that LINQ might be used in non-standard ways such as with EF Core and, as such, I don't know it some functionality would break there were MS to change it.
      However, if that is indeed the case, I think that just highlights that there's a major flaw in how LINQ is conceived and used. We're all pretending like IEnumerable is de-coupled from our various collection implementations but in practice it is incredibly coupled -- and invisibly so. It's the worst kind of design pattern.

  • @dkabracadabra
    @dkabracadabra Před 18 dny

    ok. so what causes such slowdown? 2 nested function calls, null check, 2 type tests? imho the later

  • @Rein______
    @Rein______ Před 18 dny

    Last() is an exception timebomb. I use LastOrDefault() most of the time.

  • @knixa
    @knixa Před 18 dny +1

    if I have the type I use the type, why would else would the type be there

  • @YT-dr8qi
    @YT-dr8qi Před 18 dny

    In general I find such heavy use of IEnumerable in many codebase as a bad practice. There are at least two reasons against it: double enumeration is not guaranteed and enumerables doesnt support more performant methods for some operations (e.g. last, count) which are supported by indexed collections like List or array. IEnumerable methods for such operations use O(n) time while lists do it in O(1) time.
    Yes, it's possible to determine the exact type of IEnumerable collection which you received as an input parameter and use different implications depending on this type. But is it the optimal way? In my opinion sometimes it's much better to keep the less generic type in your code instead of passing everything as IEnumerable parameter

    • @prman9984
      @prman9984 Před 18 dny +2

      Literally every IEnumerable method already does what you are saying. They do type-checks which take a few ns, but then they do the right thing every time anyway.

    • @robertmckee9272
      @robertmckee9272 Před 18 dny

      Those methods are definitely not O(n). They are slower, but constantly because of the extra type checks.

  • @lordicemaniac
    @lordicemaniac Před 18 dny +1

    I usually use most generic method from linq and i really don't like that in "some" cases linq doesn't use best performant version for underlying type. I don't get why `Any` without parameter should not call .Count>0 for those types that have that property and also why .Last does not internally use indexer, I thought that was one of wins for linq that it uses best method to do what you want. I don't want to be explicit and test every little method that has multiple ways how to write same thing in c# just to use best one, this should do language or compiller. I know that simple for loop is more performant than linq, i made peace with that because it is kind of limitation of language and how it is encoded, but i don't see any reason for Last or Any make such difference in performance.
    What is high level language good for if you have to know how it internally operates at low level. Same thing with libraries, if you have to study library source code to use it, then it is not well written.

    • @prman9984
      @prman9984 Před 18 dny +1

      "I don't get why `Any` without parameter should not call .Count>0 for those types that have that property"
      It literally does. From the source code:
      if (source is ICollection gc)
      {
      return gc.Count != 0;
      }

    • @lordicemaniac
      @lordicemaniac Před 18 dny

      @@prman9984 i was under impression that it does, but when Nick said that it does alot of stuff under hood, i was thinking that maybe i was mistaken... well then i don't see why .Last() should not do the same as [^1]

    • @Briezar
      @Briezar Před 18 dny

      @@lordicemaniac .Last() is literally the same as [^1], as it is written as [count-1] and [^1] is syntactic sugar for that. Nick mentioned this in the video. The reason it's slow is that is also has to check for type and forward calls.

  • @JamesBlack-m8v
    @JamesBlack-m8v Před 11 dny

    Isn't it premature optimization? Personally, I find .Last() shorter, more readable, and more descriptive. In most cases, the performance difference doesn't seem that significant to me.

  • @5cover
    @5cover Před 18 dny

    6:18 I don't understand why Linq methods check for IList and not IReadOnlyList - since they only need read-only access, the former should able more optimizations

    • @TubOfPower
      @TubOfPower Před 18 dny

      I know in the case of .ForEach you can modify the data, I wouldn't be surprised if in practice nobody does in the other LINQ methods but you might be able to

    • @victorfox2972
      @victorfox2972 Před 18 dny +1

      IList doesn't implement IReadOnlyList, so it's not as simple. It seems like it should, but it doesn't.

    • @victorfox2972
      @victorfox2972 Před 18 dny

      @@TubOfPower ForEach isn't part of LINQ afaik, it's just a commonly used name

    • @TubOfPower
      @TubOfPower Před 18 dny

      @@victorfox2972 I see, deceiving, it's only for List

    • @Briezar
      @Briezar Před 18 dny +1

      @@TubOfPower are you mistaking List ForEach? That's different from LINQ ForEach.

  • @smathlax
    @smathlax Před 18 dny +4

    I agree with Nick here. If you're dealing with a list or array, using LINQ just to retrieve the last element feels like you're adding useless fluff to the code.
    By all means use LINQ for an IEnumerable, but the whole point of making a type conform to the IList interface is that it gives you certain helper methods and properties - so use them, unless you have a good reason not to.

  • @YungDeiza
    @YungDeiza Před 17 dny

    Please do "When SonarLint makes you write worse .NET code".

  • @oussama7132
    @oussama7132 Před 18 dny

    do list optimal use cases with linq apply with entity framework's dbset too?

  • @asteinerd
    @asteinerd Před 18 dny +2

    ^ - Caret (said like carrot) on US Keyboards.

    • @TheTigerus
      @TheTigerus Před 17 dny

      hat - common language
      power - math language

  • @SirBenJamin_
    @SirBenJamin_ Před 18 dny

    For me, I will always use .Count-1 or .Length-1 if applicable, ..they're both just as clear as .Last(). I guess you could argue about off by one errors, but then I'd argue you should have tests in place to catch that. And although I use linq A LOT, lets not forget how much of a pain it is to debug. How many times have you converted linq to a foreach each just so you can debug it? .. sometimes I might even leave it like that if I have had to do it more than once.

    • @robertmckee9272
      @robertmckee9272 Před 18 dny

      I have never converted LINQ to a foreach so that I could debug it.

    • @SirBenJamin_
      @SirBenJamin_ Před 18 dny

      @@robertmckee9272 wow, really? .. you're a better programmer than me then :)

    • @Briezar
      @Briezar Před 18 dny

      @@SirBenJamin_ if you need to deconstruct LINQ to a foreach for debug then you're using LINQ incorrectly.

    • @SirBenJamin_
      @SirBenJamin_ Před 18 dny

      @@Briezar riiiiiiight.

    • @Briezar
      @Briezar Před 18 dny

      @@SirBenJamin_ no offense. LINQ is designed to be as less error prone as possible (hence the speed penalty). If you need to split your query for debug then it's obviously doing too much work. Cache some operation to a variable, use return block instead of lambda. I probably don't need to say all these as you explicitly said you use LINQ a lot, but from my experience, most of the time it's a developer problem rather than LINQ.

  • @fnasserebar
    @fnasserebar Před 18 dny

    Since arrays and lists start at index 0, wouldn't it be more logical if accessing the last item in a list be list[^0]?

    • @akgames848
      @akgames848 Před 18 dny

      I'd guess it's because list[^1] is supposed to be short for list[list.Count - 1] and not exactly indexing from the last.

    • @TheTigerus
      @TheTigerus Před 17 dny

      Take the axis of all numbers from 0 to infinity and put elements of length of 1 integer. 1st element exists between 0 and 0.(9). On 0 it starts, on 0.(9) it ends. When you are on the end ( X.(9) ) then to get the last element you need to get back to X, so literally "end - 1". It originates from pointers in C. When you are on the end you can append next element to the list. To get last item of the list you need to move pointer before that element to read it's memory.

  • @cruz1ale
    @cruz1ale Před 18 dny

    Generic array extension method
    ```
    static class ArrayExtensions
    {
    public static T Last(this T[] array)
    {
    if(array == null || array.Length == 0)
    {
    return default(T);
    }
    return array[array.Length - 1];
    }
    }
    ```

    • @Briezar
      @Briezar Před 18 dny +1

      you should IList instead. It works for both arrays and list.

    • @rGunti
      @rGunti Před 18 dny

      In addition to @Briezar 's advise, the return type should be `T?` and technically it should be called `LastOrDefault` 🤓
      But I also wonder why there is no extension method that specifically targets certain interfaces / types, and have `IEnumerable` as the last resort extension. But I guess there might be issues with multiple extension methods on the same type with the same name.

    • @Briezar
      @Briezar Před 18 dny +1

      @@rGunti LINQ has like 200+ extension methods accounting for overloads. List alone implements IList, IReadOnlyList, ICollection, IReadOnlyCollection, IEnumerable; I could imagine the amount of maintenance needed for this large repo.
      Besides, they're extension methods, not overridden methods. If a List is returned as IEnumerable then .Last() would still be called on the IEnumerable extension, not the IList extension. Then IEnumerable extensions would lose all of its meaning.

  • @scott98390
    @scott98390 Před 18 dny

    Why do we index arrays starting at 0 from the front but from 1 at the back?

    • @TizzyT455
      @TizzyT455 Před 18 dny

      If you think about it as a cursor moving between indexes and the actual element you are working with is on the right of the cursor ^0 would put the cursor at the end of the collection right? as in to the right of the last available element. So to the right of the cursor is out of bounds, so we move the cursor back 1 so that the element before the end of the collection is indexed, hence ^1

  • @aderitosilvachannel
    @aderitosilvachannel Před 17 dny

    Last() should be the last resort. 🙃

  • @7eper
    @7eper Před 18 dny

    Will observablecollection be indexable?

  • @szynkie6710
    @szynkie6710 Před 18 dny

    What about elementAt, similar performance to last/first?

  • @failgun
    @failgun Před 15 dny +1

    I have no real objection to someone using last() in this scenario but I'm also taken aback by the number of people saying myArray[^1] is "unreadable".
    Index/Range literals are part of the language and you not knowing ^1 is the idiom for "last valid index" is not a reason to claim it's less readable than a named method.

    • @rowbart3095
      @rowbart3095 Před 14 dny

      yeah some people are not too bright

  • @perdonomai8060
    @perdonomai8060 Před 17 dny

    What is LINQ? :P I mean... don't use it at all, it has a lot of performance issues. Only maybe in places that they are not hot paths and you need to have a more 'readable' code.

    • @TheTigerus
      @TheTigerus Před 17 dny

      Language INtegrated Query kinda SQL made for C# lists, arrays, etc.

  • @enitalp
    @enitalp Před 18 dny +1

    In-game dev LINQ Usage is prohibited on projects by most senior devs,
    It hides huge CPU costs, can't be debugged, and usually generates a lot of allocations, so GC.
    Linq is not that bad in itself; it's just the way it's easy to make a mess,
    Like, go throw all the objects, and go throw all the components of each object and compare them with all the other objects' components. To count something.
    It's all in a one-liner, so the developer gets his answer. But the problem is the method, the thinking of the algorithm. Linq allows that kind of code behavior. And while the result is wanted, the way to get to that result is to go through only some of the objects, for example. But it's easy in Linq.
    This is why the usage is prohibited.

    • @Nworthholf
      @Nworthholf Před 18 dny +2

      I dont think a real senior would just outright ban any technology in the entire project. There are hot paths and cold paths, and >95% of your codebase is cold, so why reduce readability and maintainability for no reason?

    • @Briezar
      @Briezar Před 18 dny +3

      @@enitalp LINQ only allocates when used incorrectly. Returning a new List allocates even more than just returning an IEnumerable for looping purposes. If a senior dev prohibits LINQ for this kinda reason, then I'd reconsider his status as a senior dev.
      A lot of high level Unity tutorial channels extensively use LINQ in their vids. Tarodev and git-amend are the first ones to come into my mind.

    • @kocot.
      @kocot. Před 18 dny +1

      @@Nworthholf right? the whole sentence seems kind of shady, if such practice is be considered poor performance and it was backed by evidence it would get banned company-wide or project-wide, it wouldn't be up for the dev being senior or not to decide if should be used, lol. All I hear is 'some oldschool devs had bad experiences and prefer to stay away'

  • @nickbarton3191
    @nickbarton3191 Před 18 dny

    Can't remember the last time the performance hit mattered. Yeah, lost the plot, my excuse is that I'm watching a movie.

  • @milinmt
    @milinmt Před 18 dny

    juste to make, what was the c# version that first allow this syntax [^1] ?

    • @krccmsitp2884
      @krccmsitp2884 Před 18 dny +3

      Indexer and Range were introduced in C# 8 as of September 2019.

  • @nathanstreger3851
    @nathanstreger3851 Před 18 dny

    What editor does he use? Anyone know? I want to use that "how does the compiled code look" feature he's able to do.

  • @jameshancock
    @jameshancock Před 18 dny

    The thing is, that there is low hanging fruit for the .net team. .Last() can absolutely be just as fast as [^1] (carrot!) in this case. Same with .Any(). Both should be optimized out either by the compiler or the underlying methods because if it's something with a count or length that is stored, it should know this and do exactly that and .Last() doesn't check for 0 length lists, it is already asserting that the list has at least 1 value, so there should be no difference.
    There are TONs of cases of this in .NET that immediately speeds up everyone's code, AND keeps it expressive without the ugly code.

    • @SirBenJamin_
      @SirBenJamin_ Před 18 dny +1

      This assumes you know the type at compile time though.

    • @prman9984
      @prman9984 Před 18 dny

      "The thing is, that there is low hanging fruit for the .net team. .Last() can absolutely be just as fast as [^1] (carrot!) in this case. "
      Do you really think they are idiots that haven't thought of this yet? Of course they already do this. The type check is what makes it take 8ns. If they were as dumb as you think, it would be in the ms range.
      From the published source code for Last:
      if (source is IList list)
      {
      int count = list.Count;
      if (count > 0)
      {
      found = true;
      return list[count - 1];
      }
      }
      Then if found is false, Last() throws and LastOrDefault() returns null;

  • @Gonzo345
    @Gonzo345 Před 18 dny

    Sticking with LinQ, sorry

  • @WimtenBrink
    @WimtenBrink Před 18 dny

    You make a huge mistake by mentioning LINQ and then you're not using it! LINQ is the query-syntax in C# where you say: "from item in list where condition(item) select item;". Instead of LINQ, you're showing method chaining techniques that basically do the same thing. So: "list.where(condition);" This is a very common mistake that C# developers make when they talk about LINQ.
    Can we agree to just stop calling it LINQ and just call it 'method chaining' instead?

  • @ClayBrooks1010
    @ClayBrooks1010 Před 18 dny +1

    Everyone in the comments missing the point. This isn’t index fiddling or book keeping. This is a free optimization that can be made without any effort.
    We are in the context of knowing we need the last element AND the list isn’t empty. Not “I have an indexable thing therefore I must indices all the time”
    ^1 is just as intentional as Last(). So if I know ^1 is faster, why not just do that? It’s literally free.

    • @prman9984
      @prman9984 Před 18 dny

      It's not though. ^1 assumes a list, meaning it works and gets into production and then fails when the list is finally returned empty a week later. That's very expensive, the opposite of free. Making a habit of using Linq's OrDefault() methods is very good code management. It's readable and avoids runtime errors, which are the most expensive and disruptive.

    • @johnsuckher3037
      @johnsuckher3037 Před 18 dny

      ​@@prman9984 it's about knowing OrDefault part. If the thing has to exist OrDefault is just a fluff. Often we need something to exist unless it's dealing with things outside our tight control. Clay, in video Nick mentions that Last would fail since Last indicates it has to exist as well. Recent videos of Nick are bit strange in a sense that now he's advocating for whatever benchmarks best rather than is intuitive for dotnet developers

    • @VonCarlsson
      @VonCarlsson Před 18 dny +1

      @@prman9984 If the list not being empty is a precondition of the code, then having it throw an exception if violated is almost always desirable (relatively speaking). Silently ignoring such violations is a _huge_ anti-pattern.

    • @TizzyT455
      @TizzyT455 Před 18 dny

      @@VonCarlsson I wouldn't bother, ironically the intent of the video went over these guys heads.

    • @jimv1983
      @jimv1983 Před 18 dny +2

      Because the code readability of Last() is better and we're talking about a performance difference of 8 NANOSECONDS. That's 8/1,000,000th of one millisecond. It's even more irrelevant when you consider getting the contents of that list is going to take literally millions of times longer.

  • @msdevel
    @msdevel Před 18 dny

    it's very hard to watch the fall of such a good YT channel when now it's just advertising interrupted only occasionally by good content.

  • @TizzyT455
    @TizzyT455 Před 18 dny

    All you declarative programmers on the defense in the chat really let the point of the video go over your head.

  • @trukeis856
    @trukeis856 Před 8 dny

    Who cares about nerdy stuff that wins you one stratosecond

  • @josefromspace
    @josefromspace Před 18 dny

    Highlight “Last”, ALT + . ENTER