When LINQ Makes You Write Worse .NET Code

Поделиться
HTML-код
  • Опубликовано: 16 сен 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

Комментарии • 239

  • @nickchapsas
    @nickchapsas  21 день назад +20

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

    • @paxcoder
      @paxcoder 21 день назад +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 21 день назад +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 21 день назад +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 21 день назад +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 21 день назад

      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.

  • @timlong7289
    @timlong7289 21 день назад +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 21 день назад +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".

  • @SuperLabeled
    @SuperLabeled 21 день назад +239

    Readable code > 8 nanoseconds

    • @nickchapsas
      @nickchapsas  21 день назад +23

      Exactly 😄

    • @stoino1848
      @stoino1848 21 день назад +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 21 день назад +12

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

    • @neonmidnight6264
      @neonmidnight6264 21 день назад +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 21 день назад

      @@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.

  • @ethanr0x
    @ethanr0x 21 день назад +22

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

    • @alfflasymphonyx
      @alfflasymphonyx 21 день назад

      Exactly!

    • @b33j4y
      @b33j4y 21 день назад +1

      or better yet ICollection

    • @cruz1ale
      @cruz1ale 21 день назад

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

    • @ethanr0x
      @ethanr0x 21 день назад +3

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

    • @b33j4y
      @b33j4y 21 день назад

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

  • @fusedqyou
    @fusedqyou 21 день назад +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 21 день назад +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 21 день назад

      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 13 дней назад

      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 21 день назад +72

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

    • @keyser456
      @keyser456 21 день назад +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 21 день назад +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 21 день назад +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 21 день назад

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

  • @adambickford8720
    @adambickford8720 21 день назад +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_ 21 день назад +10

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

    • @timlong7289
      @timlong7289 21 день назад +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 21 день назад

      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 21 день назад +7

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

    • @adambickford8720
      @adambickford8720 21 день назад

      @@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 20 дней назад +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 20 дней назад

      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 12 дней назад

      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 21 день назад +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 20 дней назад

      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.

  • @browny99
    @browny99 21 день назад +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

  • @DynamicalisBlue
    @DynamicalisBlue 21 день назад +27

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

    • @adambickford8720
      @adambickford8720 21 день назад +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 21 день назад +5

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

    • @bluecup25
      @bluecup25 21 день назад

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

    • @milinmt
      @milinmt 21 день назад

      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 21 день назад +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.

  • @FrancoisBothaZA
    @FrancoisBothaZA 21 день назад +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 21 день назад +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 21 день назад

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

    • @IllidanS4
      @IllidanS4 21 день назад

      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!

  • @WaldenL
    @WaldenL 21 день назад +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 19 дней назад

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

  • @logank.70
    @logank.70 21 день назад +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 21 день назад +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 21 день назад +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 21 день назад

      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..

  • @ryankueter9488
    @ryankueter9488 17 дней назад

    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.

  • @BonBaisers
    @BonBaisers 21 день назад +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 21 день назад +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 21 день назад +4

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

  • @jimv1983
    @jimv1983 21 день назад +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 13 дней назад

    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.

  • @jasoncox7244
    @jasoncox7244 21 день назад

    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.

  • @matthewsheeran
    @matthewsheeran 21 день назад +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 21 день назад +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.

  • @AronK
    @AronK 20 дней назад

    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 21 день назад

    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 15 дней назад

    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.

  • @Bliss467
    @Bliss467 21 день назад +1

    Why didn’t they simply override Last for Lists?

  • @dusrdev
    @dusrdev 21 день назад

    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.

  • @bslushynskyi
    @bslushynskyi 20 дней назад

    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.

  • @johnnyvdoremalen
    @johnnyvdoremalen 9 дней назад

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

  • @Saleca
    @Saleca 21 день назад +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

  • @RandallEike
    @RandallEike 21 день назад +2

    Early optimization is the root of all evil.

    • @nickchapsas
      @nickchapsas  21 день назад

      Not about optimisation

    • @RandallEike
      @RandallEike 21 день назад +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 :)

  • @Ilix42
    @Ilix42 21 день назад

    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.

  • @TheTigerus
    @TheTigerus 21 день назад +1

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

  • @guenolelacroix6434
    @guenolelacroix6434 21 день назад

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

    • @prman9984
      @prman9984 21 день назад

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

  • @MichaelBattaglia
    @MichaelBattaglia 20 дней назад

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

  • @padonker
    @padonker 11 дней назад

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

  • @kairuilow2226
    @kairuilow2226 21 день назад

    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.

  • @luciannaie
    @luciannaie 19 дней назад

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

  • @JamesBlack-m8v
    @JamesBlack-m8v 15 дней назад

    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.

  • @miguelgremy
    @miguelgremy 18 дней назад

    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 ?

  • @ThugLifeModafocah
    @ThugLifeModafocah 21 день назад +2

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

    • @andreaspetersen2662
      @andreaspetersen2662 20 дней назад

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

  • @xnetc9
    @xnetc9 18 дней назад

    Boss likes Linq and requires it as best practice.

  • @buriedstpatrick2294
    @buriedstpatrick2294 21 день назад +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 21 день назад

      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 17 дней назад

      @@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.

  • @knixa
    @knixa 21 день назад +1

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

  • @twiksify
    @twiksify 21 день назад

    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.

  • @gani3813
    @gani3813 21 день назад

    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?

  • @yytflo1058
    @yytflo1058 21 день назад

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

  • @bytejuggler
    @bytejuggler 12 дней назад

    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.)

  • @rcranjos
    @rcranjos 20 дней назад

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

  • @T___Brown
    @T___Brown 21 день назад

    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); 🤣🤣🤣🤣

  • @dkabracadabra
    @dkabracadabra 21 день назад

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

  • @YungDeiza
    @YungDeiza 21 день назад

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

  • @lordicemaniac
    @lordicemaniac 21 день назад +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 21 день назад +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 21 день назад

      @@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 21 день назад

      @@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.

  • @drleandrocorrea
    @drleandrocorrea 21 день назад

    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  21 день назад

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

  • @YT-dr8qi
    @YT-dr8qi 21 день назад

    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 21 день назад +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 21 день назад

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

  • @oussama7132
    @oussama7132 21 день назад

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

  • @szynkie6710
    @szynkie6710 21 день назад

    What about elementAt, similar performance to last/first?

  • @7eper
    @7eper 21 день назад

    Will observablecollection be indexable?

  • @Rein______
    @Rein______ 21 день назад

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

  • @5cover
    @5cover 21 день назад

    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 21 день назад

      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 21 день назад +1

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

    • @victorfox2972
      @victorfox2972 21 день назад

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

    • @TubOfPower
      @TubOfPower 21 день назад

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

    • @Briezar
      @Briezar 21 день назад +1

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

  • @scott98390
    @scott98390 21 день назад

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

    • @TizzyT455
      @TizzyT455 21 день назад

      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

  • @alfflasymphonyx
    @alfflasymphonyx 21 день назад

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

    • @prman9984
      @prman9984 21 день назад +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.

  • @perdonomai8060
    @perdonomai8060 21 день назад

    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 21 день назад

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

  • @asteinerd
    @asteinerd 21 день назад +2

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

    • @TheTigerus
      @TheTigerus 21 день назад

      hat - common language
      power - math language

  • @fnasserebar
    @fnasserebar 21 день назад

    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 21 день назад

      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 21 день назад

      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.

  • @milinmt
    @milinmt 21 день назад

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

    • @krccmsitp2884
      @krccmsitp2884 21 день назад +3

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

  • @SirBenJamin_
    @SirBenJamin_ 21 день назад

    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 21 день назад

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

    • @SirBenJamin_
      @SirBenJamin_ 21 день назад

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

    • @Briezar
      @Briezar 21 день назад

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

    • @SirBenJamin_
      @SirBenJamin_ 21 день назад

      @@Briezar riiiiiiight.

    • @Briezar
      @Briezar 21 день назад

      @@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.

  • @nickbarton3191
    @nickbarton3191 21 день назад

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

  • @nathanstreger3851
    @nathanstreger3851 21 день назад

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

  • @aderitosilvachannel
    @aderitosilvachannel 20 дней назад

    Last() should be the last resort. 🙃

  • @cruz1ale
    @cruz1ale 21 день назад

    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 21 день назад +1

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

    • @rGunti
      @rGunti 21 день назад

      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 21 день назад +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.

  • @smathlax
    @smathlax 21 день назад +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.

  • @failgun
    @failgun 18 дней назад +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 17 дней назад

      yeah some people are not too bright

  • @enitalp
    @enitalp 21 день назад +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 21 день назад +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 21 день назад +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. 21 день назад +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'

  • @Gonzo345
    @Gonzo345 21 день назад

    Sticking with LinQ, sorry

  • @WimtenBrink
    @WimtenBrink 21 день назад

    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?

  • @jameshancock
    @jameshancock 21 день назад

    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_ 21 день назад +1

      This assumes you know the type at compile time though.

    • @prman9984
      @prman9984 21 день назад

      "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;

  • @ClayBrooks1010
    @ClayBrooks1010 21 день назад +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 21 день назад

      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 21 день назад

      ​@@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 21 день назад +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 21 день назад

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

    • @jimv1983
      @jimv1983 21 день назад +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 21 день назад

    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 21 день назад

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

  • @trukeis856
    @trukeis856 11 дней назад

    Who cares about nerdy stuff that wins you one stratosecond

  • @josefromspace
    @josefromspace 21 день назад

    Highlight “Last”, ALT + . ENTER