Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

My take on Dependency Injection and Singleton

Discussion in 'General Discussion' started by Le_Tai, Mar 3, 2020.

  1. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    Not using namespaces in your project is quite reckless. You'll be polluting global namespace with identifiers, which will encourage clashes.

    The idea is apparently to make dependencies explicit. Similar to many other principles, overusing this one will lead to adverse/negative effect.

    For example, taken to the absurd extent, DI could mean that if you need a stringbuilder in your code, you have to provide one instead of creating it, as it is a dependency of its own. Obviosuly doing this won't improve the program in any way.

    I still feel that DI/IoC are overly fancy names for simple things...

    https://martinfowler.com/bliki/InversionOfControl.html
    https://stackoverflow.com/questions/3058/what-is-inversion-of-control
     
  2. unit_dev123

    unit_dev123

    Joined:
    Feb 10, 2020
    Posts:
    989
    Is it important to understand these concepts?
     
  3. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    Not really. Not those concepts, because you'll end up using them anyway as long as you know how to program.

    It would be more useful to learn about software design patterns:
    https://en.wikipedia.org/wiki/Software_design_pattern

    But the rule of the thumb is that you never turn yourself into a member of a cargo cult. You should understand why are you using something, and what do you gain from it.

    P.S. Interesting related article from 2002: http://www.paulgraham.com/icad.html
     
  4. unit_dev123

    unit_dev123

    Joined:
    Feb 10, 2020
    Posts:
    989
    Thank you sir I'm learning cprogramming language for text based game, and was interested in easy syntax like lua, so abstract idioms dont interest me, i am also looking to collab if you are interested?
     
    Last edited: Mar 4, 2020
  5. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    5,984
    To be fair, words are used to distinguish between things that are not fundamentally different, which don't have a clear distinction from similar things, or which differ only in a narrow context. Behaviour trees and state machines are another example.

    The problem is when people act as if it's something entirely separate.
     
  6. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    432
    They are

    Yeah, I could have made that clearer. Everything refer to objects that are meant to be shared, like those that people are using Singleton for. Not literally everything.
     
  7. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    432
    Don't learn anything to solve problem you don't have. Run into problem first, then look for solutions.
     
  8. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    I that that giving a name to a concept should make it clear what the concept is about. Standard names for standard design patterns are chosen well, and, for example, it is quite hard to misunderstand what "singleton" is referring to.

    That is not the case for inversion of control and dependency injection. Rather than simplifying the concept and making it clear and easier to understand, they give false impression that there's magical mystery behind it, muddle the definition, and encourage creation of cargo cults. "Use DI for the greater good" --> that's a bad idea.

    Inversion of control for example, refers to callbacks, visitor pattern and plugin-architectures, or GUI frameworks where main loop is not implemented by you. Dependency injection refers to ability to pass alternative implementation that will utilize polymorphism. The fancy 25$ names simply muddle the concept, make it unclear, and that's one of the reasons why OP spent so many paragraphs debunking. The name of the concept is poorly defined and unclear. Despite the thing itself being inherently simple. That's why I think it would be better if it remained unnamed.

    Inversion of Control existed at least since win16 (release date: 1992), and likely was created long before that (because unix was made in 1971, and I'd be very surprised if it didn't have a concept of callback in it).

    ---------

    I'm not interested in a collab.
    Given that you expressed interest in writing, perhaps you could try making the text game in RenPy or Twine rather than in Unity.
    While RenPy is geared towards visual novels, nothing stops you from writing zork in it.
    https://lemmasoft.renai.us/forums/viewtopic.php?t=40435
    https://github.com/DaBackpack/Text-py
    https://en.wikipedia.org/wiki/Zork
    But then again, it depends on what are you trying to do - to learn writing or to learn programming.
     
    Last edited: Mar 4, 2020
    aer0ace and Ryiah like this.
  9. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    5,984
    Maybe the problem is not that it has a name, but that it isn't universally agreed upon what that name means.

    It does seem like a case of terminology that has been muddled by being used to refer both to a general practice and a specific technique (where neither of them are properly distinct from neighboring concepts). Maybe that does mean that it was a mistake to use it in the first place.

    But on the other hand, when you have to refer to something with a bunch of words or sentences, it probably could do with a term.
     
  10. andyz

    andyz

    Joined:
    Jan 5, 2010
    Posts:
    2,148
    You are right in that is the most likely clash area - but the IDE is quiet on warnings on that front!
    I really need to learn best practices for using though. Some people organise their code folders exactly around the namespaces /offtopic
     
    Last edited: Mar 4, 2020
  11. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    I wanted to drop in a concrete case study that I have just come across for using DI (including Inversion of Control).

    I've been building my game in my free time for the last 3 years. It was only just recently that I needed the principles of DI for a feature that I'm building. For reference, it's a tile-based, turn-based tactics game.

    Naturally, you start building a game in Unity by going all-out with GameObjects, MonoBehaviours, etc. Unity is strongest with its prototyping abilities, after all. So I proceeded to build Tile, Unit, Board, BoardManager, CombatManager, FactionManager, etc, as MonoBehaviours. Sure, I could have decoupled them from MonoBehaviour at the start, but that would defeat the point of rapid prototyping, especially since there's no immediate need to. Years pass, not having a need to decouple my game classes from MonoBehaviour, so I was feeling like all was going well.

    Then I started to build my AI, and realized that the AI will also need access to the game systems (all the managers) to calculate several possible results and outcomes. BUT! My game systems manipulate Units and Tiles as MonoBehaviours which are part of the game world! How can I re-use all my game systems and operate on different Units and Tiles for AI simulation? If I kept them as MonoBehaviours, that would mean I'd have to generate an entirely new set of GameObjects, just to simulate an AI outcome for analysis. I didn't need the heaviness of GameObjects or MonoBehaviours. Simple, lightweight containers for Unit and Tile data would have sufficed. So I did that.

    I added an interface IUnit implemented by Unit (in-game MonoBehaviour) and AIUnit (lightweight AI data), and an interface ITile implemented by Tile (in-game MonoBehaviour) and AITile (lightweight AI data), and so forth for other data classes. Each of these interfaces have functions like Get/SetHealth(), Get/SetAmmo(), Get/SetTerrainType(), etc. These interfaces are then passed into the game managers, rather than their concrete counterparts, and now none of my managers need to know anything about the implementation of the objects that they're operating on!

    There are definitely a lot more details that I'm omitting from this case study, just for the sake of pointing out a clear and simple use-case for the DI principles. For example, I also had to introduce an IGame, which serves as the go-to for all of the managers. The concrete Game class and AIWorld classes then implemented IGame. The managers themselves are instantiated from the same class, even though they are two different instances. But this way, I can guarantee that their operations do exactly the same thing on the data passed to them. The side effect here is that all managers need access to IGame in one way or another, so that they can get to all of the other managers. However, I can find comfort in knowing if that IGame is the actual Game or my AIWorld.

    So the very core of DI, as described in that James Shore article, has allowed me to reuse core systems by stripping out the unnecessary Unity dependencies. In a sense, the main game "injects" Unity versions of my objects (Units, Tiles, etc) into those game systems, but my AI object "injects" lightweight versions of those objects into those same game systems. This way, all the game behavior remains intact, while they're operating on two different data sets. And as mentioned, implementing the systems this way has the nice side effect of being far more easily testable.

    There is also the completely opposite side of DI, where you have different operations, operating on the same set of data, meaning interfaces are made out of the operations instead. For example, a Unit can have a weapon interface, that calls Attack(), and if the Unit is passed a ShortRangeWeapon, Attack() does a short-range attack, and with a LongRangeWeapon, Attack() does a long-range attack. In the case study above, I didn't really have a use for this, yet, at least.

    Personally, I think if you are using interfaces in any way in your code, you probably already have a grasp of the fancily-named DI and are already using it.

    But essentially, for my case, it was a design pattern that I needed to accomplish a goal. This is how any sort of pattern in your toolbox should be used. If you don't see a need for it, then you probably don't have one yet.
     
    bobisgod234 and Ryiah like this.
  12. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,886
    I always forget that interfaces count as DI. In that case, I use DI in every single unity application I make!
     
    xVergilx likes this.
  13. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    432
    No, they do not.
     
  14. daerom

    daerom

    Joined:
    Sep 4, 2017
    Posts:
    16
    Inversion of Control is very important in large scale business applications -- and for good reason. If you're applying for a job in my industry, you better know IoC (and in turn DI) well or I'm not even going to consider hiring you. It lets you control the flow of dependencies -- meaning a class that takes a repository as a dependency shouldn't care where that repository stores/fetches its data or how its implemented, the application will "register" which one it wants to use on startup. A good benefit of this is if your repository isn't handling concurrent reads well, you can "wrap" your repository in a new version that caches and instead register that version. The consuming class doesn't know and shouldn't care. I know some of you are saying "well, I pass my dependencies into the constructor, so that is the same!", but not really. It is arguably a form of DI (sometimes called poor mans injection), but the class that is passing those dependencies is likely creating them, which breaks Inversion of Control.

    That said, I personally feel Unity is not the place to use IoC or container based DI. There is cost to having an IoC container and letting it do the resolution, and I feel I'd rather expend that performance budget elsewhere. To me, just using good design decisions and clear responsibilities is sufficient for Unity. I also use singletons for certain concepts and feel that they have their place.
     
    RecursiveFrog and JoNax97 like this.
  15. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,886
    Well yes it pretty much is, because if your using an interface then you most often will also have a client and service that need to implement that interface, and will use an injector to create a service instance and inject it into the client.

    At least, when I use interfaces I try to maintain dependency inversion l which means I also need an injector, client and service. Which in turn facilitates and is DI. As opposed to avoiding the interface part and injecting from service directly to client, which removes dependency inversion. So likely if you are using one, you using it because you also need the other.
     
  16. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    This is along the lines of what I think about when I think interfaces and DI. Interfaces are seldom useful if their concrete objects are instantiated in the same class that uses it.
     
    MadeFromPolygons likes this.
  17. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    They do, but in certain circumstances.

    If you create a class and pass "IMemoryManager" to it and then do something like calling IMemoryManager->NewObject, that's DI.
    If you have a list of ISomeObjects, and then run through it and call ISomeObject->DoTheThing(), that's not DI. Just pure polymorphism.
     
    Ryiah and aer0ace like this.
  18. RecursiveFrog

    RecursiveFrog

    Joined:
    Mar 7, 2011
    Posts:
    350
    This sounds more like an endorsement of the concept of "composition instead of inheritance" which doesn't require IoC or DI to take advantage of.
     
  19. RecursiveFrog

    RecursiveFrog

    Joined:
    Mar 7, 2011
    Posts:
    350
    Having suffered through a DI and IoC system in a non-Unity context, the thought of putting it into a Unity game feels even more misguided.

    When I think of these systems I think of them in terms of [Inject] annotations overtop IInterface fields that are populated by way of a configuration file that isn't supported natively by your IDE and that is at such a high level of abstraction that it becomes onerous to scaffold and to debug, and incurs an unusual level of mental effort to untangle every time it has to be modified since it is far removed from the daily coding of the implementations required.

    Furthermore those magical [Inject] annotations tend to contain terribly inefficient code that is opaque to the developer using them, and risk being ripped out later anyway. There's a reason Unity quickly deprecated their own [Inject] annotations in their DOTS stack.

    These systems strike me as a sort of an incarnation of Harrison Bergeron in which they attempt to raise the output of the unproductive by forcing everyone into using an ugly and unwieldy system that hinders the productivity of those who understand what they are doing. Possibly this is valid in enterprise but in game development sounds like a recipe for disaster.
     
    xVergilx, Ryiah and ippdev like this.
  20. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,800
    I agree with this. I have run into it with compsci grads who don't understand Unity as well as greybeards and look at boilerplate Unity code I have written based on hundreds of manual examples and innumerable forum and Answers replies, squint their eyes and tell me they don't understand what i am doing. Then proceed to write code that is nine levels deep in serialization, loaded up with LINQ, whipping around custom system objects and not paying one iota of attention to the frame rate dropping with each code addition...and anything needing done does not use the Inspector..one of Unity's most powerful tools. They try to multithread, create custom system objects that hang about eating ms and make a totally comprehensible Unity framework which any two year user of Unity could unravel easily.

    I had this one enterprise coder come on a job claiming he is working on a big Unity game, lots of experience..because the job required that..immediately without code review wanted to tear everything up and write his own audio, button, image interfaces. Management thought he had a clue. About a month in and pissed at this guy and the corporate back biting gambits I go to his workstation to ask him what he is working on. He has a skybox with my UI on it and is trying to "change the background image" like it is the background of a webpage. Umm..skyboxes are unity noob 101. These enterprise coder types should never be near a Unity project until they understand why Unity does what it does in the manner it does and that C# is Unity C#.
     
    xVergilx, Ryiah and Ony like this.
  21. Ony

    Ony

    Joined:
    Apr 26, 2009
    Posts:
    1,973
    threads like this remind me of how to some people it might appear that I have absolutely no idea what I'm doing, because if you test me on my programming knowledge about specific things I'm going to fail. Hard. I learned to program with BASIC and went from there. I've worked with a whole bunch of different languages since, and I couldn't tell you any particular "programmery" thing about any of them, aside from the fact that I made something with it.

    It's so odd, to realize that I have no idea what I'm doing, yet I've been doing it successfully for most of my life. I suppose I'd be able to work faster or more efficiently if I knew all of this stuff, but then again would I? Conversations like the one in this thread go right over my head, yet here I am working on yet another new game. The minutia doesn't matter to me, I guess. I have no idea if I'm using "dependency injection" or "polymorphism" or "abstraction" or "encapsulation" or "blah blah blah." I just do what I do and it works. Sometimes that makes me feel left out, because I can't "hang" with the programmers arguing back and forth about the "right" way to do something, but hey, it is how it is.

    Anyway, I guess I wanted to just throw an idea out there. To anyone reading this stuff who might be like me and thinking "what the heck are they all talking about?" it's cool. You can still do it, if you want to do it. Rules are only rules because someone decided they were. Do what you do.
     
    Last edited: Mar 5, 2020
  22. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    I don't think it's enterprise vs games, I think it's just good vs bad design and you can find that in any industry really.

    Enterprise in some areas actually pioneered a lot of the best practices games use. Data oriented design at massive scale was going on in the banking industry a long time ago. At the same time enterprise is also known for mass producing higher level business apps with poorly trained developers.

    Unity has actually been the equivalent of the bad side of enterprise for most of it's existence. Catering to low skill developers and making things easy, but at a huge cost in performance and efficiency. While other parts of the industry were quite different.

    Games just have an inherently higher bar then some other industries, you can't suck quite as much and make something that is functional. But you can still suck a whole lot.
     
    xVergilx and aer0ace like this.
  23. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    I would call this "Intuitive programming".

    I actually (used to?) write this way myself, the only issue is that some concepts defy intuitive approach and still require initial training phase where it is necessary to understand things logically, and not intuitively. C++ has plenty of things that require a book to learn them.
     
  24. Ony

    Ony

    Joined:
    Apr 26, 2009
    Posts:
    1,973
    I have (and do still) read books to learn things about languages that I need to know and use. That's how I leaned Perl and BASIC and C++ and so on and so forth (nowadays it's more web-based learning but I still prefer books). I guess the difference is that I don't really pay attention to what particular methodologies are called, I just learn to do them and then do them. Programming by its very nature requires thinking logically, of course, but I don't really think knowing the names of what each style is helps me in the end. Logical intuition, I suppose.

    I should mention that I don't have the concern of "the person who needs to use the code after me" because 99% of the time I'm the last person in the chain, and I can read my code just fine. If you are coding so others can take it over later (or during) then yes, I understand the need for following the rules more precisely.
     
    MadeFromPolygons likes this.
  25. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    The way I presented it, I have to agree with you that it starts like that. It took a while to set up the case study, since providing concrete examples of in-practice uses of DI concepts is usually a problem. The key paragraph was actually this one:

    So, IGame is passed into all services so that they have knowledge of every other service.
    With the systems having gotten torn up and refactored, I still needed to pass in some interfaces to retrieve certain data.
    So I ended up with a Board class that looks like this:

    Code (CSharp):
    1.      
    2.         public Board()
    3.         {
    4.         }
    5.  
    6.         public void Initialize(IGame game
    7.             , IPositionYSupplier posYSupplier
    8.             , ITileSupplier tileSupplier
    9.             , IStructureListSupplier structureListSupplier
    10.             , IStructureAttacher structureAttacher
    11.             , IFogOfWarSupplier fogOfWarSupplier)
    12.         {
    13.            ...
    14.         }
    15.  
    16.  
    It ends up looking horrendous, but it gets the job done, and can probably get refactored later on for more clarity. I definitely wouldn't endorse DI as a necessary design pattern for Unity projects. It shouldn't be a core principle to base your entire project on. It indeed makes the codebase unwieldy to a certain point, but again, it comes in handy when you need to architect things a certain way to get things working and moving forward.
     
  26. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    432
    OFC they work with each other, nicely, but definitely not:

     
    MadeFromPolygons likes this.
  27. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    432
    There is nothing wrong with clarity. In fact, it is beneficial in that when you have long list of dependencies like that, its DI's way of telling you that may be Board is doing too much work?

    If it is true that Board actually need all of that and can't be seperated to smaller class, then I would rather have a checklist of everything I need to set-up for the Board to work, than having it use Singleton, then just doesn't work, making me dig through the code to find out why.
     
  28. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    432
    Wait until you get into graphic programming or ML/AI. People there are master of using big word for simple concepts.
     
    Ony likes this.
  29. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,886
    Agreed, was a bad and rushed explanation but I clarified afterwards :)
     
    aer0ace and Le_Tai like this.
  30. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Not many enterprise staples actually work at all for game development. The theory can but the practise really does not. To start with, garbage allocation is a total and utter afterthrought. Enterprise is a pretty blanket term, but when I use it, I mean the surface level. These are not hyper optimised for most businesses, basically the majority is not optimised.

    Now if you're taking about database queries on a massive scale, that's a sector that gets good optimisations but only closer to the hardware layer.

    Unity's DOTS is obviously inspired somewhat by lower level optimised database work, but other than this, it's really far from classic enterprise. I can imagine an enterprise developer going "well that looks familiar" then going "well I'm not sure any enterprise customers want non OOP at surface level".

    Consider Linq alone and you think "this is pretty bad for game dev performance", and to fix that you want to recode that or work in even more boilerplate... got to be cautious of just adopting things that performed in a different situation.
     
    Ryiah, RecursiveFrog and aer0ace like this.
  31. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,886
    I literally built almost an entire prototype using LINQ as an experiement once. Im not saying it ran well, I am not even saying it ran, and the code made my eyes bleed by the time it was done, but it certainly was an interesting exercise :D

    Anyone new coming here, do not take this as meaning "try using LINQ". It means "if you need to use LINQ in your games code frequently, you probably can write that a better way"
     
    Last edited: Mar 6, 2020
  32. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    Are there any explanations or articles as to why linq queries are not great for gamedev? I use a few in my codebase and find them pretty convenient from time to time.
     
  33. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,886
    They are convenient but really really bad for performance. If you do a single one its probably fine but if your doing one inside of an update loop or a loop in general - then thats really not good.

    @aer0ace https://docs.microsoft.com/en-us/windows/mixed-reality/performance-recommendations-for-unity that is for microsoft unity guidance and talks about LINQ and why you shouldnt use it
     
    hippocoder, RecursiveFrog and aer0ace like this.
  34. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    They are creating temporary objects which are then going to be collected by GC.

    In a situation where you receive IEnumerable/IEnumerator, or in a situation where you call a delegate/lambda using a class method, the language allocates memory, and given that C# lacks ability to implement RAII, it is going to float somewhere until it is collected.

    However, in this case convenience often outweighs GC costs. Code that does not allocate tend to be verbose and and with worse readability.
     
    aer0ace and MadeFromPolygons like this.
  35. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,886
    Interestingly I have always actually found LINQ less readable! But that totally is just me, I get that I am the odd one out here :D
     
    RecursiveFrog and xVergilx like this.
  36. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    If you're talking about stuff like...
    Code (csharp):
    1.  
    2. var queryLondonCustomers = from cust in customers where cust.City == "London" select cust;
    3.  
    Then this is garbage and probably should not be used by anyone who isn't suffering from SQL withdrawal.
    If anything, it reminds me of Common Lisp's "loop"
    Code (csharp):
    1.  
    2. (loop for i from 0 downto -10 collect i)
    3.  
    4. ;;compare to:
    5. (do (( i (+ 1 i))
    6.    ((< i 10) i)
    7.    (print i))
    8.  
    Where for some reason language designer decided to demonstrate language capabilities by designing an utility piece of code which uses different rules compared to everything else.

    However, in my opinion, quasi-functional extension methods are generally well done.
    Code (csharp):
    1.  
    2. IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 6);
    3.  
    I personally feel that the idea of extension methods is one of the few things that C# got right. I feel that they result in cleaner code and better separation of data from functions that operate on it.

    I also think that ".Where(fruit => fruit.Length < 6)" expresses my intent better than if I had to manually create a temporary list, a loop and then manually populating the list using filter.
     
    xVergilx and aer0ace like this.
  37. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,303
    Same could be said for a whole bunch of unity functions.
     
    RecursiveFrog likes this.
  38. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,189
    LINQ's performance is poor compared to creating your own code for the use case.

    https://jacksondunstan.com/articles/4819

    And most queries generate garbage (6 generate no garbage, 29 generate 32 to 88 bytes, 17 generate 100+ bytes).

    https://jacksondunstan.com/articles/4840

    That said I'm completely fine using them in situations where neither of these matters, and I can always run a manual garbage collection pass before gameplay starts again.
     
    Last edited: Mar 6, 2020
    xVergilx and aer0ace like this.
  39. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    The author of the test preallocates list of correct size for "manual" methods.

    Here's what I got when I disabled preallocation:
    Code (csharp):
    1.  
    2.         stopwatch.Reset();
    3.         stopwatch.Start();
    4.         for (int it = 0; it < numIterations; ++it){
    5.             int len = array.Length;
    6.             //found = new List<int>(len); <---------- original preallocation
    7.             found = new List<int>();
    8.             for (int i = 0; i < len; ++i){
    9.                 int elem = array[i];
    10.                 if (elem == 1){
    11.                     found.Add(elem);
    12.                 }
    13.             }
    14.         }
    15.         long whereManualTime = stopwatch.ElapsedMilliseconds;
    16.  
    upload_2020-3-6_21-30-13.png
    This is for one million iterations.

    And this is ten thousand iterations with 100 kilobyte arrays:
    upload_2020-3-6_21-36-1.png

    While there's indeed a slowdown, the cost is comparable to doing an extra function call per element.
    For example, if the "checker" is replaced with double delegate, we get:
    upload_2020-3-6_21-40-35.png
    Code (csharp):
    1.  
    2.         Func<int, bool> checker = val => val == 1;
    3.         Func<int, bool> checker2 = val => checker(val);
    4.  
     

    Attached Files:

    Ryiah and aer0ace like this.
  40. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    (In response to an above question)

    I don't use LINQ in runtime anywhere, but it can pop up here and there in my editor code purely for convenience, it's really good at just grabbing data of a certain type for dirty tooling.

    I recall someone did a gc-free version of LINQ somewhere, but I just know I'd have to maintain this over the course of the project life span.
     
    Murgilod, xVergilx and aer0ace like this.
  41. aer0ace

    aer0ace

    Joined:
    May 11, 2012
    Posts:
    1,511
    Welp, looks like I know what I'm doing this weekend.
     
  42. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,189
    Slinq.

    https://github.com/pdo400/smooth.foundations
     
    hippocoder likes this.
  43. RecursiveFrog

    RecursiveFrog

    Joined:
    Mar 7, 2011
    Posts:
    350
    If you are aiming for the best numbers possible, isn't that what you'd want to do anyway? I'm not sure how that discredits or diminishes his findings since the fact that manual declarations allow you to do this very thing is one of the reasons you'd choose the option to begin with.

    It would be fun if he'd included numbers for arrays and native arrays, though.
     
  44. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    Linq does not operate on containers, it operates on sequences. That's how. Code the preallocates storage in advance is not functionally equivalent to Linq code, because length of sequence is unknown.

    A simple example:
    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using System.Linq;
    6.  
    7. static class ForEachLinqExt{
    8.     public static void ForEach<T>(this IEnumerable<T> enumeration, System.Action<T> action){
    9.         foreach(T item in enumeration){
    10.             action(item);
    11.         }
    12.     }  
    13. }
    14. public class LinqDemo2 : MonoBehaviour{
    15.     // Start is called before the first frame update
    16.  
    17.     IEnumerable<int> gen(System.Text.StringBuilder sb){
    18.         var maxRange = Random.Range(10, 1000);
    19.         for(int i = 0; i < maxRange; i++){
    20.             sb.AppendFormat("Generated: {0}\n", i);
    21.             yield return i;
    22.         }
    23.     }
    24.  
    25.     void demo(){
    26.         var sb = new System.Text.StringBuilder();
    27.  
    28.         var seq = gen(sb).Where(
    29.             var => {
    30.                 sb.AppendFormat(" Where #1(%2): {0}\n", var);
    31.                 return (var % 2) == 0;
    32.             }
    33.         ).Where(
    34.             var => {
    35.                 sb.AppendFormat("  Where #2(%4): {0}\n", var);
    36.                 return (var % 4) == 0;
    37.             }
    38.         ).Where(
    39.             var => {
    40.                 sb.AppendFormat("   Where #2(%8): {0}\n", var);
    41.                 return (var % 8) == 0;
    42.             }
    43.         );
    44.  
    45.         //seq.ForEach(var => Debug.LogFormat("Printing: {0}", var));
    46.         seq.ForEach(var => sb.AppendFormat("    Printing: {0}\n", var));
    47.         Debug.LogFormat(sb.ToString());
    48.  
    49.     }
    50.  
    51.     void Start(){
    52.         demo();
    53.     }
    54.  
    55.     // Update is called once per frame
    56.     void Update()
    57.     {
    58.  
    59.     }
    60. }
    61.  
    62.  
    Output:
    Code (csharp):
    1.  
    2. Generated: 0
    3.  Where #1(%2): 0
    4.   Where #2(%4): 0
    5.    Where #2(%8): 0
    6.     Printing: 0
    7. Generated: 1
    8.  Where #1(%2): 1
    9. Generated: 2
    10.  Where #1(%2): 2
    11.   Where #2(%4): 2
    12. Generated: 3
    13.  Where #1(%2): 3
    14. Generated: 4
    15.  Where #1(%2): 4
    16.   Where #2(%4): 4
    17.    Where #2(%8): 4
    18. Generated: 5
    19.  Where #1(%2): 5
    20. Generated: 6
    21.  Where #1(%2): 6
    22.   Where #2(%4): 6
    23. Generated: 7
    24.  Where #1(%2): 7
    25. Generated: 8
    26.  Where #1(%2): 8
    27.   Where #2(%4): 8
    28.    Where #2(%8): 8
    29.     Printing: 8
    30. Generated: 9
    31.  Where #1(%2): 9
    32. Generated: 10
    33.  Where #1(%2): 10
    34.   Where #2(%4): 10
    35. Generated: 11
    36.  Where #1(%2): 11
    37. Generated: 12
    38.  Where #1(%2): 12
    39.   Where #2(%4): 12
    40.    Where #2(%8): 12
    41. Generated: 13
    42.  Where #1(%2): 13
    43. Generated: 14
    44.  Where #1(%2): 14
    45.   Where #2(%4): 14
    46. Generated: 15
    47.  Where #1(%2): 15
    48. Generated: 16
    49.  Where #1(%2): 16
    50.   Where #2(%4): 16
    51.    Where #2(%8): 16
    52.     Printing: 16
    53. Generated: 17
    54.  Where #1(%2): 17
    55. Generated: 18
    56.  Where #1(%2): 18
    57.   Where #2(%4): 18
    58. Generated: 19
    59.  Where #1(%2): 19
    60. Generated: 20
    61.  Where #1(%2): 20
    62.   Where #2(%4): 20
    63.    Where #2(%8): 20
    64. Generated: 21
    65.  Where #1(%2): 21
    66. Generated: 22
    67.  Where #1(%2): 22
    68.   Where #2(%4): 22
    69. Generated: 23
    70.  Where #1(%2): 23
    71. Generated: 24
    72.  Where #1(%2): 24
    73.   Where #2(%4): 24
    74.    Where #2(%8): 24
    75.     Printing: 24
    76. Generated: 25
    77.  Where #1(%2): 25
    78. Generated: 26
    79.  Where #1(%2): 26
    80.   Where #2(%4): 26
    81. Generated: 27
    82.  Where #1(%2): 27
    83. Generated: 28
    84.  Where #1(%2): 28
    85.   Where #2(%4): 28
    86.    Where #2(%8): 28
    87. Generated: 29
    88.  Where #1(%2): 29
    89. Generated: 30
    90.  Where #1(%2): 30
    91.   Where #2(%4): 30
    92. Generated: 31
    93.  Where #1(%2): 31
    94. Generated: 32
    95.  Where #1(%2): 32
    96.   Where #2(%4): 32
    97.    Where #2(%8): 32
    98.     Printing: 32
    99. Generated: 33
    100.  Where #1(%2): 33
    101. Generated: 34
    102.  Where #1(%2): 34
    103.   Where #2(%4): 34
    104. Generated: 35
    105.  Where #1(%2): 35
    106. Generated: 36
    107.  Where #1(%2): 36
    108.   Where #2(%4): 36
    109.    Where #2(%8): 36
    110. Generated: 37
    111.  Where #1(%2): 37
    112. Generated: 38
    113.  Where #1(%2): 38
    114.   Where #2(%4): 38
    115. Generated: 39
    116.  Where #1(%2): 39
    117. Generated: 40
    118.  Where #1(%2): 40
    119.   Where #2(%4): 40
    120.    Where #2(%8): 40
    121.     Printing: 40
    122. Generated: 41
    123.  Where #1(%2): 41
    124. Generated: 42
    125.  Where #1(%2): 42
    126.   Where #2(%4): 42
    127. Generated: 43
    128.  Where #1(%2): 43
    129. Generated: 44
    130.  Where #1(%2): 44
    131.   Where #2(%4): 44
    132.    Where #2(%8): 44
    133. Generated: 45
    134.  Where #1(%2): 45
    135. Generated: 46
    136.  Where #1(%2): 46
    137.   Where #2(%4): 46
    138. Generated: 47
    139.  Where #1(%2): 47
    140. Generated: 48
    141.  Where #1(%2): 48
    142.   Where #2(%4): 48
    143.    Where #2(%8): 48
    144.     Printing: 48
    145. Generated: 49
    146.  Where #1(%2): 49
    147. Generated: 50
    148.  Where #1(%2): 50
    149.   Where #2(%4): 50
    150. Generated: 51
    151.  Where #1(%2): 51
    152. Generated: 52
    153.  Where #1(%2): 52
    154.   Where #2(%4): 52
    155.    Where #2(%8): 52
    156. Generated: 53
    157.  Where #1(%2): 53
    158. Generated: 54
    159.  Where #1(%2): 54
    160.   Where #2(%4): 54
    161. Generated: 55
    162.  Where #1(%2): 55
    163. Generated: 56
    164.  Where #1(%2): 56
    165.   Where #2(%4): 56
    166.    Where #2(%8): 56
    167.     Printing: 56
    168. Generated: 57
    169.  Where #1(%2): 57
    170. Generated: 58
    171.  Where #1(%2): 58
    172.   Where #2(%4): 58
    173. Generated: 59
    174.  Where #1(%2): 59
    175. Generated: 60
    176.  Where #1(%2): 60
    177.   Where #2(%4): 60
    178.    Where #2(%8): 60
    179. Generated: 61
    180.  Where #1(%2): 61
    181. Generated: 62
    182.  Where #1(%2): 62
    183.   Where #2(%4): 62
    184. Generated: 63
    185.  Where #1(%2): 63
    186. Generated: 64
    187.  Where #1(%2): 64
    188.   Where #2(%4): 64
    189.    Where #2(%8): 64
    190.     Printing: 64
    191. Generated: 65
    192.  Where #1(%2): 65
    193. Generated: 66
    194.  Where #1(%2): 66
    195.   Where #2(%4): 66
    196. Generated: 67
    197.  Where #1(%2): 67
    198. Generated: 68
    199.  Where #1(%2): 68
    200.   Where #2(%4): 68
    201.    Where #2(%8): 68
    202. Generated: 69
    203.  Where #1(%2): 69
    204. Generated: 70
    205.  Where #1(%2): 70
    206.   Where #2(%4): 70
    207. Generated: 71
    208.  Where #1(%2): 71
    209. Generated: 72
    210.  Where #1(%2): 72
    211.   Where #2(%4): 72
    212.    Where #2(%8): 72
    213.     Printing: 72
    214. Generated: 73
    215.  Where #1(%2): 73
    216. Generated: 74
    217.  Where #1(%2): 74
    218.   Where #2(%4): 74
    219. Generated: 75
    220.  Where #1(%2): 75
    221. Generated: 76
    222.  Where #1(%2): 76
    223.   Where #2(%4): 76
    224.    Where #2(%8): 76
    225. Generated: 77
    226.  Where #1(%2): 77
    227. Generated: 78
    228.  Where #1(%2): 78
    229.   Where #2(%4): 78
    230. Generated: 79
    231.  Where #1(%2): 79
    232. Generated: 80
    233.  Where #1(%2): 80
    234.   Where #2(%4): 80
    235.    Where #2(%8): 80
    236.     Printing: 80
    237. Generated: 81
    238.  Where #1(%2): 81
    239. Generated: 82
    240.  Where #1(%2): 82
    241.   Where #2(%4): 82
    242. Generated: 83
    243.  Where #1(%2): 83
    244. Generated: 84
    245.  Where #1(%2): 84
    246.   Where #2(%4): 84
    247.    Where #2(%8): 84
    248. Generated: 85
    249.  Where #1(%2): 85
    250. Generated: 86
    251.  Where #1(%2): 86
    252.   Where #2(%4): 86
    253. Generated: 87
    254.  Where #1(%2): 87
    255. Generated: 88
    256.  Where #1(%2): 88
    257.   Where #2(%4): 88
    258.    Where #2(%8): 88
    259.     Printing: 88
    260. Generated: 89
    261.  Where #1(%2): 89
    262. Generated: 90
    263.  Where #1(%2): 90
    264.   Where #2(%4): 90
    265. Generated: 91
    266.  Where #1(%2): 91
    267. Generated: 92
    268.  Where #1(%2): 92
    269.   Where #2(%4): 92
    270.    Where #2(%8): 92
    271. Generated: 93
    272.  Where #1(%2): 93
    273. Generated: 94
    274.  Where #1(%2): 94
    275.   Where #2(%4): 94
    276. Generated: 95
    277.  Where #1(%2): 95
    278. Generated: 96
    279.  Where #1(%2): 96
    280.   Where #2(%4): 96
    281.    Where #2(%8): 96
    282.     Printing: 96
    283. Generated: 97
    284.  Where #1(%2): 97
    285. Generated: 98
    286.  Where #1(%2): 98
    287.   Where #2(%4): 98
    288. Generated: 99
    289.  Where #1(%2): 99
    290. Generated: 100
    291.  Where #1(%2): 100
    292.   Where #2(%4): 100
    293.    Where #2(%8): 100
    294. Generated: 101
    295.  Where #1(%2): 101
    296. Generated: 102
    297.  Where #1(%2): 102
    298.   Where #2(%4): 102
    299. Generated: 103
    300.  Where #1(%2): 103
    301. Generated: 104
    302.  Where #1(%2): 104
    303.   Where #2(%4): 104
    304.    Where #2(%8): 104
    305.     Printing: 104
    306. Generated: 105
    307.  Where #1(%2): 105
    308. Generated: 106
    309.  Where #1(%2): 106
    310.   Where #2(%4): 106
    311. Generated: 107
    312.  Where #1(%2): 107
    313. Generated: 108
    314.  Where #1(%2): 108
    315.   Where #2(%4): 108
    316.    Where #2(%8): 108
    317. Generated: 109
    318.  Where #1(%2): 109
    319. Generated: 110
    320.  Where #1(%2): 110
    321.   Where #2(%4): 110
    322. Generated: 111
    323.  Where #1(%2): 111
    324. Generated: 112
    325.  Where #1(%2): 112
    326.   Where #2(%4): 112
    327.    Where #2(%8): 112
    328.     Printing: 112
    329. Generated: 113
    330.  Where #1(%2): 113
    331. Generated: 114
    332.  Where #1(%2): 114
    333.   Where #2(%4): 114
    334. Generated: 115
    335.  Where #1(%2): 115
    336. Generated: 116
    337.  Where #1(%2): 116
    338.   Where #2(%4): 116
    339.    Where #2(%8): 116
    340. Generated: 117
    341.  Where #1(%2): 117
    342. Generated: 118
    343.  Where #1(%2): 118
    344.   Where #2(%4): 118
    345. Generated: 119
    346.  Where #1(%2): 119
    347. Generated: 120
    348.  Where #1(%2): 120
    349.   Where #2(%4): 120
    350.    Where #2(%8): 120
    351.     Printing: 120
    352. Generated: 121
    353.  Where #1(%2): 121
    354. Generated: 122
    355.  Where #1(%2): 122
    356.   Where #2(%4): 122
    357. Generated: 123
    358.  Where #1(%2): 123
    359. Generated: 124
    360.  Where #1(%2): 124
    361.   Where #2(%4): 124
    362.    Where #2(%8): 124
    363. Generated: 125
    364.  Where #1(%2): 125
    365. Generated: 126
    366.  Where #1(%2): 126
    367.   Where #2(%4): 126
    368. Generated: 127
    369.  Where #1(%2): 127
    370. Generated: 128
    371.  Where #1(%2): 128
    372.   Where #2(%4): 128
    373.    Where #2(%8): 128
    374.     Printing: 128
    375. Generated: 129
    376.  Where #1(%2): 129
    377. Generated: 130
    378.  Where #1(%2): 130
    379.   Where #2(%4): 130
    380. Generated: 131
    381.  Where #1(%2): 131
    382. Generated: 132
    383.  Where #1(%2): 132
    384.   Where #2(%4): 132
    385.    Where #2(%8): 132
    386. Generated: 133
    387.  Where #1(%2): 133
    388. Generated: 134
    389.  Where #1(%2): 134
    390.   Where #2(%4): 134
    391. Generated: 135
    392.  Where #1(%2): 135
    393. Generated: 136
    394.  Where #1(%2): 136
    395.   Where #2(%4): 136
    396.    Where #2(%8): 136
    397.     Printing: 136
    398. Generated: 137
    399.  Where #1(%2): 137
    400. Generated: 138
    401.  Where #1(%2): 138
    402.   Where #2(%4): 138
    403. Generated: 139
    404.  Where #1(%2): 139
    405. Generated: 140
    406.  Where #1(%2): 140
    407.   Where #2(%4): 140
    408.    Where #2(%8): 140
    409. Generated: 141
    410.  Where #1(%2): 141
    411. Generated: 142
    412.  Where #1(%2): 142
    413.   Where #2(%4): 142
    414. Generated: 143
    415.  Where #1(%2): 143
    416. Generated: 144
    417.  Where #1(%2): 144
    418.   Where #2(%4): 144
    419.    Where #2(%8): 144
    420.     Printing: 144
    421. Generated: 145
    422.  Where #1(%2): 145
    423. Generated: 146
    424.  Where #1(%2): 146
    425.   Where #2(%4): 146
    426. Generated: 147
    427.  Where #1(%2): 147
    428. Generated: 148
    429.  Where #1(%2): 148
    430.   Where #2(%4): 148
    431.    Where #2(%8): 148
    432. Generated: 149
    433.  Where #1(%2): 149
    434. Generated: 150
    435.  Where #1(%2): 150
    436.   Where #2(%4): 150
    437. Generated: 151
    438.  Where #1(%2): 151
    439. Generated: 152
    440.  Where #1(%2): 152
    441.   Where #2(%4): 152
    442.    Where #2(%8): 152
    443.     Printing: 152
    444. Generated: 153
    445.  Where #1(%2): 153
    446. Generated: 154
    447.  Where #1(%2): 154
    448.   Where #2(%4): 154
    449. Generated: 155
    450.  Where #1(%2): 155
    451. Generated: 156
    452.  Where #1(%2): 156
    453.   Where #2(%4): 156
    454.    Where #2(%8): 156
    455. Generated: 157
    456.  Where #1(%2): 157
    457. Generated: 158
    458.  Where #1(%2): 158
    459.   Where #2(%4): 158
    460. Generated: 159
    461.  Where #1(%2): 159
    462. Generated: 160
    463.  Where #1(%2): 160
    464.   Where #2(%4): 160
    465.    Where #2(%8): 160
    466.     Printing: 160
    467. Generated: 161
    468.  Where #1(%2): 161
    469. Generated: 162
    470.  Where #1(%2): 162
    471.   Where #2(%4): 162
    472. Generated: 163
    473.  Where #1(%2): 163
    474. Generated: 164
    475.  Where #1(%2): 164
    476.   Where #2(%4): 164
    477.    Where #2(%8): 164
    478. Generated: 165
    479.  Where #1(%2): 165
    480. Generated: 166
    481.  Where #1(%2): 166
    482.   Where #2(%4): 166
    483. Generated: 167
    484.  Where #1(%2): 167
    485. Generated: 168
    486.  Where #1(%2): 168
    487.   Where #2(%4): 168
    488.    Where #2(%8): 168
    489.     Printing: 168
    490. Generated: 169
    491.  Where #1(%2): 169
    492. Generated: 170
    493.  Where #1(%2): 170
    494.   Where #2(%4): 170
    495. Generated: 171
    496.  Where #1(%2): 171
    497. Generated: 172
    498.  Where #1(%2): 172
    499.   Where #2(%4): 172
    500.    Where #2(%8): 172
    501. Generated: 173
    502.  Where #1(%2): 173
    503. Generated: 174
    504.  Where #1(%2): 174
    505.   Where #2(%4): 174
    506. Generated: 175
    507.  Where #1(%2): 175
    508. Generated: 176
    509.  Where #1(%2): 176
    510.   Where #2(%4): 176
    511.    Where #2(%8): 176
    512.     Printing: 176
    513. Generated: 177
    514.  Where #1(%2): 177
    515. Generated: 178
    516.  Where #1(%2): 178
    517.   Where #2(%4): 178
    518. Generated: 179
    519.  Where #1(%2): 179
    520. Generated: 180
    521.  Where #1(%2): 180
    522.   Where #2(%4): 180
    523.    Where #2(%8): 180
    524. Generated: 181
    525.  Where #1(%2): 181
    526. Generated: 182
    527.  Where #1(%2): 182
    528.   Where #2(%4): 182
    529. Generated: 183
    530.  Where #1(%2): 183
    531. Generated: 184
    532.  Where #1(%2): 184
    533.   Where #2(%4): 184
    534.    Where #2(%8): 184
    535.     Printing: 184
    536. Generated: 185
    537.  Where #1(%2): 185
    538. Generated: 186
    539.  Where #1(%2): 186
    540.   Where #2(%4): 186
    541. Generated: 187
    542.  Where #1(%2): 187
    543. Generated: 188
    544.  Where #1(%2): 188
    545.   Where #2(%4): 188
    546.    Where #2(%8): 188
    547. Generated: 189
    548.  Where #1(%2): 189
    549. Generated: 190
    550.  Where #1(%2): 190
    551.   Where #2(%4): 190
    552. Generated: 191
    553.  Where #1(%2): 191
    554. Generated: 192
    555.  Where #1(%2): 192
    556.   Where #2(%4): 192
    557.    Where #2(%8): 192
    558.     Printing: 192
    559. Generated: 193
    560.  Where #1(%2): 193
    561. Generated: 194
    562.  Where #1(%2): 194
    563.   Where #2(%4): 194
    564. Generated: 195
    565.  Where #1(%2): 195
    566. Generated: 196
    567.  Where #1(%2): 196
    568.   Where #2(%4): 196
    569.    Where #2(%8): 196
    570. Generated: 197
    571.  Where #1(%2): 197
    572. Generated: 198
    573.  Where #1(%2): 198
    574.   Where #2(%4): 198
    575. Generated: 199
    576.  Where #1(%2): 199
    577. Generated: 200
    578.  Where #1(%2): 200
    579.   Where #2(%4): 200
    580.    Where #2(%8): 200
    581.     Printing: 200
    582. Generated: 201
    583.  Where #1(%2): 201
    584. Generated: 202
    585.  Where #1(%2): 202
    586.   Where #2(%4): 202
    587. Generated: 203
    588.  Where #1(%2): 203
    589. Generated: 204
    590.  Where #1(%2): 204
    591.   Where #2(%4): 204
    592.    Where #2(%8): 204
    593. Generated: 205
    594.  Where #1(%2): 205
    595. Generated: 206
    596.  Where #1(%2): 206
    597.   Where #2(%4): 206
    598. Generated: 207
    599.  Where #1(%2): 207
    600. Generated: 208
    601.  Where #1(%2): 208
    602.   Where #2(%4): 208
    603.    Where #2(%8): 208
    604.     Printing: 208
    605. Generated: 209
    606.  Where #1(%2): 209
    607. Generated: 210
    608.  Where #1(%2): 210
    609.   Where #2(%4): 210
    610. Generated: 211
    611.  Where #1(%2): 211
    612. Generated: 212
    613.  Where #1(%2): 212
    614.   Where #2(%4): 212
    615.    Where #2(%8): 212
    616. Generated: 213
    617.  Where #1(%2): 213
    618. Generated: 214
    619.  Where #1(%2): 214
    620.   Where #2(%4): 214
    621. Generated: 215
    622.  Where #1(%2): 215
    623. Generated: 216
    624.  Where #1(%2): 216
    625.   Where #2(%4): 216
    626.    Where #2(%8): 216
    627.     Printing: 216
    628. Generated: 217
    629.  Where #1(%2): 217
    630. Generated: 218
    631.  Where #1(%2): 218
    632.   Where #2(%4): 218
    633. Generated: 219
    634.  Where #1(%2): 219
    635. Generated: 220
    636.  Where #1(%2): 220
    637.   Where #2(%4): 220
    638.    Where #2(%8): 220
    639. Generated: 221
    640.  Where #1(%2): 221
    641. Generated: 222
    642.  Where #1(%2): 222
    643.   Where #2(%4): 222
    644. Generated: 223
    645.  Where #1(%2): 223
    646. Generated: 224
    647.  Where #1(%2): 224
    648.   Where #2(%4): 224
    649.    Where #2(%8): 224
    650.     Printing: 224
    651. Generated: 225
    652.  Where #1(%2): 225
    653. Generated: 226
    654.  Where #1(%2): 226
    655.   Where #2(%4): 226
    656. Generated: 227
    657.  Where #1(%2): 227
    658. Generated: 228
    659.  Where #1(%2): 228
    660.   Where #2(%4): 228
    661.    Where #2(%8): 228
    662. Generated: 229
    663.  Where #1(%2): 229
    664. Generated: 230
    665.  Where #1(%2): 230
    666.   Where #2(%4): 230
    667. Generated: 231
    668.  Where #1(%2): 231
    669. Generated: 232
    670.  Where #1(%2): 232
    671.   Where #2(%4): 232
    672.    Where #2(%8): 232
    673.     Printing: 232
    674. Generated: 233
    675.  Where #1(%2): 233
    676. Generated: 234
    677.  Where #1(%2): 234
    678.   Where #2(%4): 234
    679. Generated: 235
    680.  Where #1(%2): 235
    681. Generated: 236
    682.  Where #1(%2): 236
    683.   Where #2(%4): 236
    684.    Where #2(%8): 236
    685. Generated: 237
    686.  Where #1(%2): 237
    687. Generated: 238
    688.  Where #1(%2): 238
    689.   Where #2(%4): 238
    690. Generated: 239
    691.  Where #1(%2): 239
    692. Generated: 240
    693.  Where #1(%2): 240
    694.   Where #2(%4): 240
    695.    Where #2(%8): 240
    696.     Printing: 240
    697. Generated: 241
    698.  Where #1(%2): 241
    699. Generated: 242
    700.  Where #1(%2): 242
    701.   Where #2(%4): 242
    702. Generated: 243
    703.  Where #1(%2): 243
    704. Generated: 244
    705.  Where #1(%2): 244
    706.   Where #2(%4): 244
    707.    Where #2(%8): 244
    708. Generated: 245
    709.  Where #1(%2): 245
    710. Generated: 246
    711.  Where #1(%2): 246
    712.   Where #2(%4): 246
    713. Generated: 247
    714.  Where #1(%2): 247
    715. Generated: 248
    716.  Where #1(%2): 248
    717.   Where #2(%4): 248
    718.    Where #2(%8): 248
    719.     Printing: 248
    720. Generated: 249
    721.  Where #1(%2): 249
    722. Generated: 250
    723.  Where #1(%2): 250
    724.   Where #2(%4): 250
    725. Generated: 251
    726.  Where #1(%2): 251
    727. Generated: 252
    728.  Where #1(%2): 252
    729.   Where #2(%4): 252
    730.    Where #2(%8): 252
    731. Generated: 253
    732.  Where #1(%2): 253
    733. Generated: 254
    734.  Where #1(%2): 254
    735.   Where #2(%4): 254
    736. Generated: 255
    737.  Where #1(%2): 255
    738. Generated: 256
    739.  Where #1(%2): 256
    740.   Where #2(%4): 256
    741.    Where #2(%8): 256
    742.     Printing: 256
    743. Generated: 257
    744.  Where #1(%2): 257
    745. Generated: 258
    746.  Where #1(%2): 258
    747.   Where #2(%4): 258
    748. Generated: 259
    749.  Where #1(%2): 259
    750. Generated: 260
    751.  Where #1(%2): 260
    752.   Where #2(%4): 260
    753.    Where #2(%8): 260
    754. Generated: 261
    755.  Where #1(%2): 261
    756. Generated: 262
    757.  Where #1(%2): 262
    758.   Where #2(%4): 262
    759. Generated: 263
    760.  Where #1(%2): 263
    761. Generated: 264
    762.  Where #1(%2): 264
    763.   Where #2(%4): 264
    764.    Where #2(%8): 264
    765.     Printing: 264
    766. Generated: 265
    767.  Where #1(%2): 265
    768. Generated: 266
    769.  Where #1(%2): 266
    770.   Where #2(%4): 266
    771. Generated: 267
    772.  Where #1(%2): 267
    773. Generated: 268
    774.  Where #1(%2): 268
    775.   Where #2(%4): 268
    776.    Where #2(%8): 268
    777. Generated: 269
    778.  Where #1(%2): 269
    779. Generated: 270
    780.  Where #1(%2): 270
    781.   Where #2(%4): 270
    782. Generated: 271
    783.  Where #1(%2): 271
    784. Generated: 272
    785.  Where #1(%2): 272
    786.   Where #2(%4): 272
    787.    Where #2(%8): 272
    788.     Printing: 272
    789. Generated: 273
    790.  Where #1(%2): 273
    791. Generated: 274
    792.  Where #1(%2): 274
    793.   Where #2(%4): 274
    794. Generated: 275
    795.  Where #1(%2): 275
    796. Generated: 276
    797.  Where #1(%2): 276
    798.   Where #2(%4): 276
    799.    Where #2(%8): 276
    800. Generated: 277
    801.  Where #1(%2): 277
    802. Generated: 278
    803.  Where #1(%2): 278
    804.   Where #2(%4): 278
    805. Generated: 279
    806.  Where #1(%2): 279
    807. Generated: 280
    808.  Where #1(%2): 280
    809.   Where #2(%4): 280
    810.    Where #2(%8): 280
    811.     Printing: 280
    812. Generated: 281
    813.  Where #1(%2): 281
    814. Generated: 282
    815.  Where #1(%2): 282
    816.   Where #2(%4): 282
    817. Generated: 283
    818.  Where #1(%2): 283
    819. Generated: 284
    820.  Where #1(%2): 284
    821.   Where #2(%4): 284
    822.    Where #2(%8): 284
    823. Generated: 285
    824.  Where #1(%2): 285
    825. Generated: 286
    826.  Where #1(%2): 286
    827.   Where #2(%4): 286
    828. Generated: 287
    829.  Where #1(%2): 287
    830. Generated: 288
    831.  Where #1(%2): 288
    832.   Where #2(%4): 288
    833.    Where #2(%8): 288
    834.     Printing: 288
    835. Generated: 289
    836.  Where #1(%2): 289
    837. Generated: 290
    838.  Where #1(%2): 290
    839.   Where #2(%4): 290
    840. Generated: 291
    841.  Where #1(%2): 291
    842. Generated: 292
    843.  Where #1(%2): 292
    844.   Where #2(%4): 292
    845.    Where #2(%8): 292
    846. Generated: 293
    847.  Where #1(%2): 293
    848. Generated: 294
    849.  Where #1(%2): 294
    850.   Where #2(%4): 294
    851. Generated: 295
    852.  Where #1(%2): 295
    853. Generated: 296
    854.  Where #1(%2): 296
    855.   Where #2(%4): 296
    856.    Where #2(%8): 296
    857.     Printing: 296
    858. Generated: 297
    859.  Where #1(%2): 297
    860. Generated: 298
    861.  Where #1(%2): 298
    862.   Where #2(%4): 298
    863. Generated: 299
    864.  Where #1(%2): 299
    865. Generated: 300
    866.  Where #1(%2): 300
    867.   Where #2(%4): 300
    868.    Where #2(%8): 300
    869. Generated: 301
    870.  Where #1(%2): 301
    871. Generated: 302
    872.  Where #1(%2): 302
    873.   Where #2(%4): 302
    874. Generated: 303
    875.  Where #1(%2): 303
    876. Generated: 304
    877.  Where #1(%2): 304
    878.   Where #2(%4): 304
    879.    Where #2(%8): 304
    880.     Printing: 304
    881. Generated: 305
    882.  Where #1(%2): 305
    883. Generated: 306
    884.  Where #1(%2): 306
    885.   Where #2(%4): 306
    886. Generated: 307
    887.  Where #1(%2): 307
    888. Generated: 308
    889.  Where #1(%2): 308
    890.   Where #2(%4): 308
    891.    Where #2(%8): 308
    892. Generated: 309
    893.  Where #1(%2): 309
    894. Generated: 310
    895.  Where #1(%2): 310
    896.   Where #2(%4): 310
    897. Generated: 311
    898.  Where #1(%2): 311
    899. Generated: 312
    900.  Where #1(%2): 312
    901.   Where #2(%4): 312
    902.    Where #2(%8): 312
    903.     Printing: 312
    904. Generated: 313
    905.  Where #1(%2): 313
    906. Generated: 314
    907.  Where #1(%2): 314
    908.   Where #2(%4): 314
    909. Generated: 315
    910.  Where #1(%2): 315
    911. Generated: 316
    912.  Where #1(%2): 316
    913.   Where #2(%4): 316
    914.    Where #2(%8): 316
    915. Generated: 317
    916.  Where #1(%2): 317
    917. Generated: 318
    918.  Where #1(%2): 318
    919.   Where #2(%4): 318
    920. Generated: 319
    921.  Where #1(%2): 319
    922. Generated: 320
    923.  Where #1(%2): 320
    924.   Where #2(%4): 320
    925.    Where #2(%8): 320
    926.     Printing: 320
    927. Generated: 321
    928.  Where #1(%2): 321
    929. Generated: <message truncated>
    930.  
    Check how messages are ordered.
     
  45. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    To add to my previous post.

    The point of Linq is that it operates on generators/sequences and allows daisy-chaining them with variable parameters.

    Container or function output can be treated in similar manner and converted into some sort of FIFO/conveyor which you can pass around and which will have relatively little memory overhead.

    In the example I posted previously, the generator chain is stored in seq object, and NOTHING is really called prior to the point where I call seq.ForEach() extension. This is easy to verify:
    Code (csharp):
    1.  
    2.         sb.AppendFormat("Before foreach\n");
    3.         seq.ForEach(var => sb.AppendFormat("    Printing: {0}\n", var));
    4.         sb.AppendFormat("After foreach\n");
    5.         Debug.LogFormat(sb.ToString());
    6.  
    Output:
    Code (csharp):
    1.  
    2. Before foreach
    3. Generated: 0
    4.  Where #1(%2): 0
    5.   Where #2(%4): 0
    6.    Where #2(%8): 0
    7.     Printing: 0
    8. Generated: 1
    9.  
    At this point, the chain start pushing values through from gen() and they get filtered by where#1, where#2, where#3, and finally arrive at the last lambda that prints them. Each part of the chain maintains its own internal state, can still access variables outside of it (even local ones), and for all things considered, gen() is still going through the loop and hasn't returned from the function. And all of this is done without declaring a class (yield took care of that). Basically, any function that takes IEnumerable<type> and returns IEnumerable<type> can be turned into part of the sequence easily. The stack of filters can be applied to any data type, containers, or even IO streams, even infinite ones.

    And that's the point of using Linq extension methods. The price is cost of few extra function calls. While the manual loop ends up faster, it does not have the same readability, and does not present an object you can pass around.
     
  46. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    OMG, so much aggression and misunderstanding in this thread. The OP article wasn't written very well indeed, but it does convey the main idea that DI is a very simple concept and if you're a programmer with some experience you're probably already using it.

    1. DI is about providing dependencies to classes from the outside (via constructor or method argument) as opposed to instantiating specific dependency class or using Singleton within the class. Use it when you have multiple implementations of the same interface that you use in some class. Other use case is when the creation of this dependency is complex and you just want to keep your user-class simple (S in SOLID) then you extract this creation to some factory method/class outside and pass the ready-to-use object via DI. There may be more use cases, but the main idea is that there's got to be a practical reason to use it, just like everything else in programming.

    2. Using DI does not mean you have to pass all dependencies via DI. That's just nonsense. Real world programming is all about practical benefits for your specific project needs and everything you use (programming pattern or paradigm, a library or a game engine) is just a tool to achieve your goal. It's a common pitfall of startup programmers who just learned some patterns to over-use them. This is equally as bad as writing strongly coupled spaghetti code.

    3. You don't have to use DI container frameworks (like Zenject) to use DI. It's just another tool.

    4. If you don't understand the benefits of DI-frameworks, congratulations - your specific project don't need it. Go and make your awesome game. When (if ever) you'll need to use one, you will know.

    Also I really cringe at some comments above saying things like "there's no place for this and that in gamedev". It seems there's a large category of Unity developers who mostly work solo for many years making games and apps and over time they build their own ways of developing and programming that's sometimes very far what is generally understood as best practice in programming world. And when they suddenly have to work in teams with other people - a lot of friction occurs. Because they just don't know how to effectively work in teams.

    The point I really want to stress is that please, don't generalize! Game development, like any other, is a very broad field with a wide range of projects in terms of development cycles, complexity, budgets, team sizes etc. For hobbyist, solo code-ninjas or startup 1-3 people indies, a lot of coding practices from the "enterprise world" does not make any sense and forcing to use them without real benefit for the project is stupid. But some people work as professionals in studios.

    Also remember that not all code is gameplay code in game dev. Studios might have large code bases that provide editor tooling, common libraries for non-gameplay features of their games, not to mention backend development which may not be using Unity at all, but it is still a part of a game project. And in all this non-gameplay code many best practices from the "big software world" do apply.

    IMHO, SOLID and DRY principles should almost always be used even in gameplay code. And in any non-critical parts of code (that's not running every frame or not running during gameplay at all) you can use every tool in C# programmer's toolset (delegates, LINQ, abstractions etc.) to reduce development and debugging time and making code more robust.
     
    Last edited: Mar 7, 2020
    SwingWren and aer0ace like this.
  47. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,337
    I actually woudln't recommend SOLID and DRY, as overuse of those practices can reduce code readability because they will spawn huge number of abstractions for no reason.

    The practice I'd advise are YAGNI, KISS and Murphy's law. "Code is written for other people to read it" is another good one to keep in mind. For anything else you need to make sure you understand the benefits, and make sure there are benefits in the first place.

    ---

    Also there wasn't any aggression in this thread. Just the usual idea exchange, pretty civil, really...
     
    Last edited: Mar 7, 2020
    aer0ace, RecursiveFrog and Ryiah like this.
  48. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    My advice was to use those practices, not over-use them. Notice the difference. There's always a way to mis-use any tool when it's not giving you any benefits. SOLID and DRY (and KISS as well) is just a baseline way to write code that has less bugs, easier to maintain etc at practically no cost in development time (if you've learned these principles by heart). It applies to all kinds of development, unless you're testing some ideas and writing temporary code that won't be used more than a day or two.

    I just want people to avoid falling into a misconception that in gamedev you are somehow better off without most good programming practices, because for some reason they "don't apply". They do, and you will suffer (extra development time and less quality end product) if you religiously avoid them.
     
    aer0ace likes this.
  49. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,800
    Please don't lecture us. Your perceptions and our intent are not aligned,. Note that you are going on about back end data structures. We are not talking about that. Generalizing the specifics to make your case is not valid. Your last paragraph is indicative to me you come from enterprise coding. If you brought a two year brilliant technical artist/game designer intern into your Unity project they would likely not be able to unravel your structures. I agree with @neginfinity here. Said intern artist would then have to visit your desk anytime he wanted something in code to change. Maybe you need to understand teams instead of potshotting at your fellow game dev peers. In my experience enterprise coders coming in new to Unity totally ignore the component based frame dependent architecture and think they can monkey wrench their enterprise practices into the codebase.

    It seems to me that the development of ECS/DOTS hilites the fragility nightmares that can arise when stepping outside the component framework. Unity has the source code and they have a tough time integrating things. Long time game devs can not easily transition over in many cases depending on what they want to accomplish. Some think they can just replace systems in Unity with their codebase. Fine..and when their arse is down the road and the company has to hire some new Unity devs..should they have to pay for those new devs to get tutored in the subtleties of such code bases? Or Unity's API changed and those code structures become invalid. Stick to the Unity paradigm and they can come in rolling instead of dead stopped as they try to unravel non-inspector based class structures and determine intent.
     
    RecursiveFrog likes this.
  50. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,800
    So why is DI good programming practice? I still do not see the benefits. It looks to me like..oh crap..I have structured myself into a corner..here..let me do this DI triple somersault wth a 1.5 twist..that'll take care of it! Your follow on assumption is that we avoid such structures because we don't understand good programming practices. I avoid programming practices that introduce dropping in framerates and hinder my ability to make rapid changes. I also know I am handing off my work to often less experienced folks and I want to make sure they can read, debug and add to it. I stick to Unity methodologies to make that simpler for my clients to integrate my work.