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

Delegate for an instance method without attaching it to a specific instance?

Discussion in 'Scripting' started by UnbridledGames, Mar 19, 2021.

  1. UnbridledGames

    UnbridledGames

    Joined:
    May 12, 2020
    Posts:
    139
    This is probably a better question for Stack Overflow, but honestly, that place is terrifying. I legit have "they are gonna crucify me for asking a dumb question or not finding the already-answered question" anxiety. Judging from memes on r/ProgrammerHumor, I console myself knowing that I am not alone.

    Ok so here's my C# question that some googling lead me nowhere on.

    I want to have a delegate for a method of a class, that could later be applied to an instance of that class that doesn't necessarily exist at the time the delegate is created.

    Not sure I'm explaining it well. Let's try an example.

    I have a class:
    public class ExampleClass
    {
    private int blahBlah;
    public int moreBlah() {
    return blahBlah + 1;
    }
    }


    I want to define a delegate that I can call on instances of ExampleClass to run moreBlah(), but when the delegate is created, I don't have a specific ExampleClass to tie it to. I want to be able to run the delegate on any ExampleClass instance.

    The reason I need to do this is I'm trying to give some unified functionality to a bunch of unrelated classes without retooling all of these other classes. I could implement an interface, sure, but then I'd need to go back and change all these other classes to support the interface, which then would necessitate a lot of other changes as well as it cascades.

    Being able to do a if(I'm working on ExampleClass) { use this delegate for any instances } would be nice.

    Other info not mentioned: I'm looking to do it this way because of some other extensibility I'm planning on, and being able to use delegates in this way would help in that regard. Plus, learning new things.

    Thanks
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,755
    Just use a static method with the first argument being an instance of the thing you want stuff applied to, and then produce your wrapper to inject the instance of the thing.

    If you have a class
    Yarg
    with an instance method called
    void foo(int arg1, string arg2);
    , generally all that means is two things:

    1. the ACTUAL function is:
    static void foo( Yarg this, int arg1, int arg2);


    2. and unadorned class instance identifiers inside the function will automatically have
    this.
    prepended to them.

    C# and IL folks might have more nuance to add to this, but you can model it this way in your head and proceed accordingly.

    You would of course have to make that static class IN the actual
    Yarg
    class if it needed to transact with any non-public things in that class, plus you'd have to make a "this" alias and use it everywhere you want to do it.
     
    mangosauce_ likes this.
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    A delegate that can call a method on some target:
    Code (csharp):
    1. var d = (t) => t.someBlah();
    2.  
    3. var obj = new ExampleClass();
    4. d(obj);
    And if you want to add/extend a class with new methods you can always check out extension methods which is basically what @Kurt-Dekker is describing but with nice added syntax sugar that lets you call it like a member method:
    https://docs.microsoft.com/en-us/do...g-guide/classes-and-structs/extension-methods

    Code (csharp):
    1.  
    2. public static class Extensions
    3. {
    4.     public static void DoStuff(this Blargh instance, int value)
    5.     {
    6.         instance.someValue += value;
    7.     }
    8. }
    9.  
    10. //elsewhere:
    11. var obj = new Blargh();
    12. obj.DoStuff(5); //the compiler will notice DoStuff is not a member of Blargh, but will find the extension method in Extensions, and will call it instead with obj as the first parameter
    13.  
    Extension methods can even be created for interfaces which is nice:
    Code (csharp):
    1.  
    2. public interface IFoo
    3. {
    4.     int someValue {get; set;}
    5. }
    6.  
    7. public static class Extensions
    8. {
    9.     public static void DoStuff(this IFoo foo, int value)
    10.     {
    11.         foo.someValue += value;
    12.     }
    13. }
    14.  
    15. public class ConcreteFoo : IFoo
    16. {
    17.     public int someValue {get; set;}
    18. }
    19.  
    20. //elsewhere
    21. var obj = new ConcreteFoo();
    22. obj.DoStuff(5);
    23.  
    Noting of course if you create a delegate from these extension methods, you have to pass in the instance as an arg:
    Code (csharp):
    1. var d = Extensions.DoStuff;
    2. var foo = new ConcreteFoo();
    3. d(foo, 5);
    4.  
    And also you can always just call extension methods like regular static methods:
    Code (csharp):
    1. var foo = new ConcreteFoo();
    2. Extensions.DoStuff(foo, 5);

    And lastly... if you're on .net 4.x you can use dynamic in the delegate example to create a single delegate that takes all objects:
    Code (csharp):
    1.  
    2. public class Foo
    3. {
    4.     public void DoStuff()
    5.     {
    6.         Debug.Log(nameof(Foo));
    7.     }
    8. }
    9.  
    10. public class Bar
    11. {
    12.     public void DoStuff()
    13.     {
    14.          Debug.Log(nameof(Bar));
    15.     }
    16. }
    17.  
    18. //elsewhere
    19. System.Action<dynamic> d = (dynamic o) => {
    20.     try //wrapping in try-catch incase you accidentally pass in an o that doesn't have 'DoStuff' on it
    21.     {
    22.         o.DoStuff();
    23.     }
    24.     catch {}
    25. };
    26. var a = new Foo();
    27. var b = new Bar();
    28. var c = new Vector3();
    29. d(a);
    30. d(b);
    31. d(c); //nothing happens since Vector3 doesn't have a 'DoStuff' method
    32.  
    I have no idea how dynamic will react with IL2CPP, never tested that myself.
     
    Last edited: Mar 19, 2021
  4. UnbridledGames

    UnbridledGames

    Joined:
    May 12, 2020
    Posts:
    139
    I'm well versed in extension methods, but they won't really work in my case without adding interfaces. I'd also PREFER to avoid using dynamic types, I've read a bunch about their overhead, which in my case, I'm trying to be extremely careful with.

    That being said - the first example you gave, that I quoted above, doesn't work, unless I'm really missing something about its use.

    Error: CS0815: Cannot assign lambda expression to an implicitly-typed variable

    If I try assigning it to anything other than var (Action, a delegate, etc) Rider complains with: Cannot convert source type 'lambda expression' to target type 'whatever_type_I_try'. So am I missing something? Because it'd be fantastic if that worked, but it feels like that breaks the hell out of strict typing.

    And did Kurt post a reply then delete it? You refer to what he said, but yours is the only reply.
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    sorry, I just free-handed my examples because I wasn't writing this in an IDE... we need to declare a type otherwise the compiler won't know what type t is. Do this:
    Code (csharp):
    1. System.Action<ExampleClass> d = (t) => t.someBlah();
    or
    Code (csharp):
    1. var d = (ExampleClass t) => t.someBlah();
    or
    Code (csharp):
    1. var d = new System.Action<ExampleClass>((t) => t.someBlarh());
    ...

    Also, what are you actually trying to do specifically?

    Maybe I can help you with your exact specific problem rather than the vague generalized explanation from your OP.

    Are you trying to create some delegate that'll support ANY type? Like basically my dynamic example, but without using dynamic? Cause if so, that's not really an option without resorting to dynamic... since doing so breaks strict typing. The compiler wouldn't know what 't' is, thus no strictness, thus dynamic being your work around which tells the compiler to "let the runtime figure it out when it gets there".

    But yeah, what will this be used for? Maybe a different design could facilitate your end goal.

    ...

    Also, can you not see Kurt's post? It's still there for me.
     
    Last edited: Mar 19, 2021