Unity Community


Page 1 of 3 123 LastLast
Results 1 to 20 of 45

  1. Location
    Paris
    Posts
    3,730

    C# delegates, I love you ...

    For confirmed devs, it can be obvious, but for people like me who come from more "simplistic" languages (javascript, ActionScript, Java, etc), their true use discovery just made me cheer like a soccer fan during finals, and say ... "goddamn it, why didn't I know this earlier ?"

    So I wanted to share the experience, so other people who learn C# will not make the same mistake as me
    (I'm not claiming to know everything about delegates, so if this post need corrections, feel free)

    In short, delegates. That is the basic.
    You can read the page, but to sum it up, delegate is attributing a method execution to a variable at runtime. So in short, imagine you have a huge core function, SpawnEnemy(), where at some point you want to spawn very different enemies with very different behaviours, under very different conditions. Instead of writing a 5000 lines long if () {} else if () {} else if () {} crap sauce, you just insert a delegate (let's say, OnSpawnEnemy()). Then for each new SpawnEnemy() called, you can reattribute the OnSpawnEnemy delegate to a different function from another script, depending on which condition is met (indeed, OnSpawEnemy reattribution has to happen before its execution).
    For example, at time 25f, some manager reattributes OnSpawnEnemy = CreateTrolls to create Trolls, at time 50f OnSpawnEnemy = CreateOrcs to create Orcs, etc,. Then whenever I call OnSpawnEnemy(), it will create the defined type of enemy.

    But I found this MSDN tutorial page wasn't doing much justice to their real power ...

    Two things I found which were totally awesome :

    1) Anonymous delegates : I don't know if it's some coder porn syndrome, but I love this one. In short, instead of hardwriting tons of different separate functions for each delegate case you want, you just write OnSpawnEnemy = delegate { //stuffToDo } . It's a must for structure visibility. The only downside is that what's inside the delegate doesn't save conditional variables. For example : for (int i = 0; i < 5; i++) OnSpawnEnemy = delegate { Debug.Log(i); } will always display 4. So you have to hardwrite variables. But it's not a big deal really, as you're not supposed to create super complicated anonymous delegate bodies (for debugging's sake).
    What I like with anonymous delegates is that you can "hack" a core behaviour at any given time under whatever condition you want, just by writing a quick subscript. Visually, the fact of not declaring a whole new function will 100% fit the very contextual nature of the change, keeping your core architecture clean.

    2) the most important one : the += and -= operators. So let's take our OnSpawnEnemy(). What if at some time, I want to spawn 4 Trolls AND 4 Orcs ? You just write :
    Code:  
    1. OnSpawnEnemy += CreateTrolls;
    2. OnSpawnEnemy += CreateOrcs;
    3. OnSpawnEnemy(4);
    And if later on, you don't want to spawn Orcs anymore, just write :
    Code:  
    1. OnSpawnEnemy -= CreateOrcs;
    As said in 1), the real power here is to make you able to hack a generic, core function. When you have a highly complex architecture, it's always better to merge redundant operations into core functions, so you trim the code noise for better clarity. It's better for debugging, too. For example with Orcs and Trolls, instead of creating 2 separate full creation functions, you merge every critical common code into SpawnEnemy (like memory management, common asset instancing, common variables like HP, strength etc declarations, etc). And then you create the "exoticisation" of your enemy in specific, separate functions that you integrate in the core one, like written above.
    At first, I was using virtual/override methods to fit each different injection. But what if I wanted to create 2 merged overrides within the same virtual entry ? I had to create another virtual into the first virtual, or one after another, etc .... VIRTUALCEPTION ! a nightmare And most of all, a huge waste of code space as you have to declare your empty virtual functions ...
    But here with delegates, no need to declare useless virtual empty functions.. just to call OnSpawnEnemy() (+test if null beforehand), and add/retrieve as many hacks as I want with a single += operator, from wherever I want.

    ________________

    Conclusion : I don't know if delegates were created (or function pointers in C) with videogames in mind, but it's clearly a must for behaviour management, and totally fits the nature of videogames code dynamic logic.
    Want to add a powerup to that punch ? No problem. OnStrikePunch += PowerUp;
    Want to retrieve that wall bounce testing for that ball ? No problem. OnBallHitTest -= TestWalls;

    I can also see how powerful delegates can be in a collaborative environment. The architect creates core code "waypoints" as delegates, and then each dev can control what type of behaviour he wants to add inside that waypoint.


    Anyway, just to wanted to share my nerdiness for delegates, but feel free to add anything you want about them, like other particularly useful scenarios, etc.
    Oh and did I mention that delegate were fast ?
    (That was my first fear when I realized how powerful they were)
    Last edited by n0mad; 09-07-2012 at 05:39 AM.



  2. Location
    Paris
    Posts
    3,730
    Quote Originally Posted by npsf3000 View Post
    Now read up on Lambda's, Action & Func.
    But I'm only lvl 33 ..
    Thanks C# is really feeling like an endless Ali Baba's cave for efficiency, stability AND simplicity. Why isn't it more widespread in videogame dev jobs ?
    (granted I didn't really extensively test C++, but it seems like C# is evolving much faster)
    Last edited by n0mad; 09-07-2012 at 05:41 AM.


  3. Posts
    620
    That's one of the reasons I completely switched to C# within Unity. I wish I could switch to C# in my day to day work, too

  4. Code Maker

    Location
    Canberra
    Posts
    849
    Now lets talk about the bad side of delegates.... They play havoc with the GC unless you know what you are doing. Lets go through 2 specific use cases:

    1) You add a delegate that references something large in memory (Texture2D for example). You hook this delegate up to an event and then it's all good. Nice callbacks and everything. Now for some reason the container for the delegate goes away (the game object holding the Texture2D). You have never removed the reference to the delegate on the event. The Texture2D is still referenced. It will stay around until the class holding the event goes away. As it's very common for these classes to be 'Managers' of some type it's likely that your Texture will never be GC'd

    2) You use anonymous delegates, the delegate references a Texture2D. There is NO WAY to remove the delegate from an event. You are back in situation 1.

    For the forthcoming GUI system we decided to implement 'better' delegates that work by holding weak references and play nice with the GC. They will work throughout unity and can also be serialised. They are not ready for release yet but I think they are pretty cool.

    If you are going to use c# delegates try and avoid anon delegates. Also be really careful with memory overhead in using some of c#'s 'nice' features like linq and foreach and the like. Use the profiler and make sure to keep your allocations in check.
    Have you tried the Strumpy Shader Editor?


  5. Location
    Montréal Quebec
    Posts
    2,241
    Delegates are really powerful but use it wisely! And C# is my fav programming language!
    Forgotten Memories Teaser
    Forgotten Memories Trailer
    I write shaders for the community!
    Dead Strike iOS (Top iOS game made with Unity).
    Nintendo Wii U and Sony (PS3/PS4/Vita) Licensed Developer/Publisher.
    __________________________________________________
    Georges Paz
    http://www.forgottenmemoriesthegame.com


  6. Location
    Ryazan, Russia
    Posts
    781
    Quote Originally Posted by n0mad View Post
    The only downside is that what's inside the delegate doesn't save conditional variables. For example :
    Code:  
    1. for (int i = 0; i < 5; i++)
    2.     OnSpawnEnemy = delegate { Debug.Log(i); }
    will always display 4.
    This code makes no sense, at least I don't see any, but if you want to get 01234, you can write it like this:

    Code:  
    1. for (int i = 0; i < 5; i++)
    2. {
    3.     int i2 = i;
    4.     OnSpawnEnemy += delegate { Debug.Log(i2); };
    5. }
    6. OnSpawnEnemy(); // prints 0 1 2 3 4

    The temporary local variable is necessary, otherwise you'll get 55555 or maybe something else you don't expect too see. If you wonder why, read about closures and what they are close over.

    ---
    If at some point you decide to subtract your delegates, be careful:

    Code:  
    1. class Program
    2. {
    3.     static void Main(string[] args)
    4.     {
    5.         Action a = A;
    6.         Action b = B;
    7.         Action c = C;
    8.  
    9.         Action x = (a + b + c) - a - c;
    10.         x(); // prints B
    11.         Console.WriteLine();
    12.  
    13.         Action y = (a + b + c) - (a + c);
    14.         y(); // still prints ABC, because ABC sequence doesn't contain AC
    15.         Console.WriteLine();
    16.  
    17.         Console.ReadLine();
    18.     }
    19.  
    20.     private static void A() { Console.Write('A'); }
    21.     private static void B() { Console.Write('B'); }
    22.     private static void C() { Console.Write('C'); }
    23. }


  7. Location
    Paris
    Posts
    3,730
    @Stramit : Thanks for the infos ! There's clearly a danger here ... I should check that out now ...

    @Alexzzzz : the code was just for quick explanation purpose And thanks for your tips too ! I didn't grab that boolean logic yet, that's great info.

    So in the end, should I deduce that Virtual/Override are safer concerning memory and GC ?
    Therefore better for an intensive function triggering ? (like state manager)
    Last edited by n0mad; 09-07-2012 at 09:10 AM.


  8. Location
    Paris
    Posts
    3,730
    @Stramit :
    Quote Originally Posted by stramit View Post
    1) You add a delegate that references something large in memory (Texture2D for example). You hook this delegate up to an event and then it's all good. Nice callbacks and everything. Now for some reason the container for the delegate goes away (the game object holding the Texture2D). You have never removed the reference to the delegate on the event. The Texture2D is still referenced. It will stay around until the class holding the event goes away. As it's very common for these classes to be 'Managers' of some type it's likely that your Texture will never be GC'd
    Now I'm afraid
    If I understood correctly, you mention there that when a container (gameobject) is destroyed, the references in his components are not ? (assuming they're not referenced anywhere else)
    Is it like that ? For example I have let's say a "system" gameObject, where I put a bunch of Texture2D references, all loaded with Resources.Load. If I destroy this System gameObject, those Texture2D references won't be cleared ? :O
    Shouldn't it be the case ?

  9. Code Maker

    Location
    Canberra
    Posts
    849
    Well the references will be GC'd so long as nothing else is referencing them. The thing is by adding delegates the delegate can reference the fields and they will not get GC'd.
    Have you tried the Strumpy Shader Editor?



  10. Location
    Ryazan, Russia
    Posts
    781
    A brief example:

    Code:  
    1. public class FooManager : MonoBehaviour
    2. {
    3.     public Action actions = null;
    4.  
    5.     public void DoActions()
    6.     {
    7.         actions();
    8.     }
    9.  
    10.     // ...
    11. }

    Code:  
    1. public class Foo : MonoBehaviour
    2. {
    3.     private byte[] hugeAmountOfData = new byte[100000000];
    4.     private FooManager manager;
    5.  
    6.     private void Start()
    7.     {
    8.         manager = (FooManager) FindObjectOfType(typeof (FooManager));
    9.         manager.actions += DoSomethingUseful;
    10.     }
    11.  
    12.     private void DoSomethingUseful()
    13.     {
    14.         //...
    15.     }
    16.  
    17.     // ...
    18.  
    19.     private void OnDestroy()
    20.     {
    21.         manager.actions -= DoSomethingUseful; /* You need this, because 'actions' delegate
    22.                                               * still contains a reference to this Foo instance,
    23.                                               * which means the instance will always stay alive
    24.                                               * and won't be collected by the GC. */
    25.     }
    26. }


  11. Location
    Paris
    Posts
    3,730
    Ok. I understand the logic, but, shouldn't a Destroy() (in an ideal world) turn every instance of it to null?
    Like here, calling manager.actions() should ideally make a NullReferenceException ? (if we don't substract to manager's delegate)


  12. Location
    Ryazan, Russia
    Posts
    781
    Code:  
    1. var referenceToInstance = new GameObject("Lucky");
    2. var secondReferenceToTheInstance = referenceToInstance;
    3. var andAnotherOne = referenceToInstance;
    4.  
    5. DestroyImmediate(referenceToInstance); /* Releases some internal resources that
    6.     the instance were using, but it doesn't destroy/erase/eliminate the instance
    7.     * completely, only the garbage collector can do it. But at this moment the GC
    8.     * still can't collect the object, because there are three references to it.
    9.     * There's no any magical way that Destroy() could set all the variables to null,
    10.     * they are still pointing at the halfdead object. */
    11.  
    12. Debug.Log(referenceToInstance == null); /* Prints 'true', because UnityEngine.Object
    13. * overrides the comparison and returns 'true' if the object is marked as destroyed.
    14. * It doesn't mean that the reference is actually null. */
    15. Debug.Log(ReferenceEquals(referenceToInstance, null)); // Prints 'false' (that's the truth :)
    16.  
    17. referenceToInstance = null;
    18. secondReferenceToTheInstance = null;
    19. andAnotherOne = null;
    20.  
    21. /* Now there are no live references to the object and the GC is able to collect it. */
    22.  
    23. Debug.Log(ReferenceEquals(referenceToInstance, null)); // Prints 'true'


  13. Posts
    416
    Gotta love C#

    I didn't know about Action and Func. I've been writing my own delegate definitions here, and looks like I may have reinvented the wheel a few times, heh.

    Cheers
    The limit of the willing suspension of disbelief for a given element is directly proportional to the element's coolness.


  14. Location
    Paris
    Posts
    3,730
    Ok thank you Alex, that makes sense now.
    If we wanted Destroy to take full control of that nullification, there should be a "ref" keyword as object parameter I suppose.
    Last edited by n0mad; 09-07-2012 at 12:35 PM.


  15. Location
    Toulouse, France
    Posts
    36
    When you do a += to register a delegate you must always add a -= to the same delegate otherwise the game will leak like hell.

    By the way delegate are not that fast, they are slow actually. But indeed it is very powerful and useful for event management.

    You made a example with Spawning enemies, but instead of using delegate, i prefere here to use an Interface (with a spawn function) and to implement decorator pattern to add different logic to the spawn.

    http://en.wikipedia.org/wiki/Decorator_pattern


  16. Posts
    308
    Welcome to continuations/closures.

    BTW JavaScript supports this and, while I haven't used Java in 15 years, I would assume it does too though it might be hackish, I'm not really sure. In fact most languages support this sort of thing though it might take a bit of thinking-outside-the-box to get it done.


  17. Location
    Gold Coast, Australia
    Posts
    3,593
    Quote Originally Posted by HarvesteR View Post
    Gotta love C#

    I didn't know about Action and Func. I've been writing my own delegate definitions here, and looks like I may have reinvented the wheel a few times, heh.

    Cheers
    Thought someone would find them useful. C# started off with hideously verbose delegates and slowly made them more and more usable.

    For those interested in finding out more about C#'s awesomeness C# in Depth is a great book. My list of everyday cool things are:

    • Extention Methods
    • Lambda's
    • Linq
    • IEnumerator/IEnumerable
    • Named & Optional paramaters. Oh, and Params.
    • Nullables
    • Generics


    I don't declare delegates often, but I do like mucking around with them on occasion:

    Code:  
    1. delegate _ _();
    2. _ _ = null; _ = () => _;
    3.  
    4. _()()()()()()()()()()()()()()()()()();


  18. Location
    Paris
    Posts
    3,730
    Thanks for the link Cripple

    Delegate are slow ? Meh, I'm confused, I read the exact opposite on Stackoverflow ? They were benchmarking Virtual/Override vs delegates, and results showed same perf ?
    edit : here it is : http://stackoverflow.com/questions/2...tes-vs-methods
    Most voted answer benchmarks delegates as faster than interfaces.

Page 1 of 3 123 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •