Search Unity

Question Retrieving the reference of a class instance from one of its methods that's passed as a parameter

Discussion in 'Scripting' started by Danixadem, Feb 21, 2023.

  1. Danixadem

    Danixadem

    Joined:
    Jul 31, 2018
    Posts:
    16
    I think code sample will explain itself.

    NOTES:

    1. Module constructor shall not need any other parameter, such as "this" referring to a Container instance

    2. Module constructor only parametre must stay as IEnumerator, not Func<> etc

    3. Coroutine passed as the parameter, will not be invoked directly (for ex. StartCoroutine(...) )

    4. Code logic that's inside the Module CTOR shall not be moved into a seperate method (neither public nor extension method).

      class Module
      {
      // CTOR
      public Module(IEnumerator _someCoroutine)
      {
      // This is where I need to get the reference to the
      // instance of a Container class (not "typeof(Container)")
      // which _someCoroutine method's reference is held,
      // directly from _someCoroutine parameter
      }

      }

      class Container : MonoBehaviour
      {
      Module module = new Module(SomeCoroutine());

      IEnumerator SomeCoroutine()
      {...}
      }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    I have absolutely no idea what you want or what you're even trying to accomplish with the above.

    Just know that constructors MAY NOT BE USED with UnityEngine-derived classes, full stop end of story.

    If you just want a coroutine runner there's tons of those up on the web and they're easy enough to make.

    Otherwise, here is how to report your problem productively in the Unity3D forums:

    http://plbm.com/?p=220

    This is the bare minimum of information to report:

    - what you want (as a ___ I want to use ___ to accomplish ___)
    - what you tried
    - what you expected to happen
    - what actually happened, especially any errors you see
    - links to documentation you used to cross-check your work (CRITICAL!!!)

    You may edit your post above.
     
  3. Danixadem

    Danixadem

    Joined:
    Jul 31, 2018
    Posts:
    16

    Mate, are you sure that you inspected my code and read the commented section at all?? Module class is NOT inherited from MonoBehaviour.
    Eventhough the question looks easy, if you knew how hard it is to solve that question, you might have easily guessed that I already knew that tons of coroutine runners are out there. What I'm trying to do is a MORE advanced version of them. If you are gonna provide some help, do so. Otherwise dont clutter the topic. In other forums, people didnt have hard time in grasping the question.
     
    Last edited: Feb 21, 2023
  4. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,003
    I think I know what he wants to do. Though I can make this short and just say: That's not possible :)

    I think he wants access to "this" of the Container instance inside his Module instance. However since the Module is created in a field initializer, that's impossible. During a field initializer the constructor of the Container was not called yet. So there's simply no way to access the instance that doesn't exist yet (or to be more precise: that hasn't been initialized yet).

    The code in question would not even work since that coroutine is an instance method of the Container class. You can not access any instance member (neither methods nor fields) of a class from inside a field initializer.

    There would be a way when the module is created inside the Containers constructor (or to be Unity conform, inside Awake). Though it's impossible to do that from a field initializer. I also tried to implement a component wrapper that allows automatic / lazy initialization when it's first accessed. However for that the wrapper would need access to the class instance it is defined in. This would require to pass "this" as an argument. That wouldn't be an issue in my case, however it's simply not possible to do that.
     
  5. Danixadem

    Danixadem

    Joined:
    Jul 31, 2018
    Posts:
    16
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,003
    Maybe I still wasn't clear enough. The main issue in your code is this:
    upload_2023-2-21_22-37-15.png

    This line is not valid as you try to use
    this.SomeCoroutine()
    inside the constructor of your Module class inside a field initializer. No matter what you do, you can not get this kind of code to work. The only methods and fields you can use inside a field initializer are static methods or fields. Though those do not belong to any instance.

    To make your construct work at all, you would need to do this:

    Code (CSharp):
    1. public class Container : MonoBehaviour
    2. {
    3.     Module module;
    4.  
    5.     IEnumerator SomeCoroutine()
    6.     {
    7.         yield return null;
    8.     }
    9.  
    10.     void Awake()
    11.     {
    12.         module = new Module(SomeCoroutine());
    13.     }
    14. }
    To answer your second part of your question: Yes you can extract the reference to the class instance that the coroutine statemachine class belongs to. However this has to be done through reflection and depends on the used compiler. The compiler generated statemachine class is an implementation detail that doesn't need to look anything alike.

    Note that a coroutine object does not necessarily contain a "this" reference. That is only the case when you actually use any field or method of the outer class. If you don't do this, the statemachine object would not have any reference to the class that created it. The compiler decides which fields are necessary.

    For example, this is the statemachine that the compiler generated for my "empty" coroutine I used here.
    Code (CSharp):
    1. // Container.<SomeCoroutine>d__1
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Diagnostics;
    6. using System.Runtime.CompilerServices;
    7.  
    8. [CompilerGenerated]
    9. private sealed class <SomeCoroutine>d__1 : IEnumerator<object>, IEnumerator, IDisposable
    10. {
    11.     private int <>1__state;
    12.  
    13.     private object <>2__current;
    14.  
    15.     object IEnumerator<object>.Current
    16.     {
    17.         [DebuggerHidden]
    18.         get
    19.         {
    20.             return <>2__current;
    21.         }
    22.     }
    23.  
    24.     object IEnumerator.Current
    25.     {
    26.         [DebuggerHidden]
    27.         get
    28.         {
    29.             return <>2__current;
    30.         }
    31.     }
    32.  
    33.     [DebuggerHidden]
    34.     public <SomeCoroutine>d__1(int <>1__state)
    35.     {
    36.         this.<>1__state = <>1__state;
    37.     }
    38.  
    39.     [DebuggerHidden]
    40.     void IDisposable.Dispose()
    41.     {
    42.     }
    43.  
    44.     private bool MoveNext()
    45.     {
    46.         switch (<>1__state)
    47.         {
    48.         default:
    49.             return false;
    50.         case 0:
    51.             <>1__state = -1;
    52.             <>2__current = null;
    53.             <>1__state = 1;
    54.             return true;
    55.         case 1:
    56.             <>1__state = -1;
    57.             return false;
    58.         }
    59.     }
    60.  
    61.     bool IEnumerator.MoveNext()
    62.     {
    63.         //ILSpy generated this explicit interface implementation from .override directive in MoveNext
    64.         return this.MoveNext();
    65.     }
    66.  
    67.     [DebuggerHidden]
    68.     void IEnumerator.Reset()
    69.     {
    70.         throw new NotSupportedException();
    71.     }
    72. }
    73.  

    The "coroutine" method itself inside the Container class just looks like this:
    Code (CSharp):
    1. [IteratorStateMachine(typeof(<SomeCoroutine>d__1))]
    2. private IEnumerator SomeCoroutine()
    3. {
    4.     return new <SomeCoroutine>d__1(0);
    5. }
    6.  
    As you can see, there is no "this" passed to the newly created class when you call your SomeCoroutine in this case. That's because the code inside the coroutine does not reference or use anything from the Container class. Therefore the statemachine does not have a reference to the Container instance.

    However when I actually use something from the Container class, the compiler adds a this field and of course initializes it. The new coroutine method would look like this:
    Code (CSharp):
    1. [IteratorStateMachine(typeof(<SomeCoroutine>d__2))]
    2. private IEnumerator SomeCoroutine()
    3. {
    4.     return new <SomeCoroutine>d__2(0)
    5.     {
    6.         <>4__this = this
    7.     };
    8. }
    Here it actually sets the "<>4__this" field to this Container instance, so it has a reference to it.

    Your actual usecase is not clear. Since your "Module" is essentially unaware where that coroutine may come from, it can not really make any assumptions what kind of references that coroutine object may contain. Coroutines can be static "methods" as well in which case the generated object also would not have any reference. So like Kurt said, you should provide more details about your usecase. Whatever it is, it would be a really hacky solution anyways.
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,003
    Just for completeness, here's my coroutine crash course which may help to understand how coroutines actually work and what they actually are.

    To actually extract a potential this reference from a coroutine object, you can do something like this:

    Code (CSharp):
    1. using System.Reflection;
    2. class Module
    3. {
    4.     public Module(IEnumerator _someCoroutine)
    5.     {
    6.         var type = _someCoroutine.GetType();
    7.         var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    8.         foreach(var f in fields)
    9.         {
    10.             if (f.Name.Contains("__this"))
    11.             {
    12.                 object o = f.GetValue(_someCoroutine);
    13.             }
    14.             // or
    15.             // if (typeof(MonoBehaviour).IsAssignableFrom(f.FieldType))
    16.         }
    17.     }
    18. }
    19.  
    The code assumes that the name of the field we're looking for contains "__this". Of course if you're looking for some other kind of reference that may be stored in the object, you can filter for it like I've shown in the comment. Be warned that almost every local variable of your original coroutine method usually ends up as an instance field of that statemachine object. So if you temporarily store references inside local variables, they would end up as instance fields as well.

    Note that you can use ILSpy or another decompiler to see what the compiler actually has generated (I used it to extract the code of the statemachine above).
     
    zombiegorilla and mopthrow like this.
  8. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,003
    I just came across this once more and I realised that the title of the thread does not really reflect what the code actually does. The code passes a coroutine object (IEnumerator) to the method while the title reads
    In the title it's claimed that the method is passed as parameter. That is possible with an actual delegate type. For example the predefined types
    System.Action
    or
    System.Func
    . In that case, when the actual method reference points to an instance method, you can always extract the object instance it belongs to that method by reading the Target property:

    Code (CSharp):
    1.     void Start()
    2.     {
    3.         TestMethod(SomeMethod);
    4.     }
    5.  
    6.     void TestMethod(System.Action aMethod)
    7.     {
    8.         object o = aMethod.Target;
    9.     }
    10.     void SomeMethod()
    11.     {
    12.     }
    13.  
    Here we actually pass a delegate that points to SomeMethod into the TestMethod. Inside TestMethod we could call the passed delegate by doing
    aMethod();
    or like shown in the code above you can read the Target property to get the instance of the method. Of course when you pass a static method, the Target property would return null.
     
  9. Danixadem

    Danixadem

    Joined:
    Jul 31, 2018
    Posts:
    16
    Thanks for your help, but I surely know that Module ctor must be run in Awake or Start. I just didint write it in the sample code for the sake of simplicity. But you got what I'm triying to achieve.

    This code actually worked. Now what I'm trying to do is to distinguish the IEnumerator methods with the same name but different parametre signature. I appreciate your help, thank you.
     
    Last edited: Feb 23, 2023
  10. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,003
    Based on what exactly? Based on the IEnumerator instance? You do realise that the object that is created by your method does not really have much to do with the method that created it. In fact you can create such an object yourself, without any "method". The yield keyword allows you to write so called "generator" methods. That's what your coroutine is. When you call your method it simply returns a statemachine object that represents the transformed logic as an iterator object.

    Yes, arguments that are passed to such a generator method will end up as private fields inside the object as they need to be stored in that object. Usually method arguments are stored on the stack / stack frame that is created when you enter the method and is removed when you leave / exit the method. Since a statemachine needs to remember its state it has to store them in that object.

    Though that object also needs to store any other local variable that is used inside the "original method" since they also need to be preserved. As I said the naming of those fields is up to the compiler as it's an implementation detail. Though it seems that internal fields usually contain angle brackets
    <>
    in the name while the original method arguments are stored with their actual name. Though the order of those fields does not reflect the order of the original method signature. So it's impossible to derive the "exact" signature from the generated object.

    For example if you have two overloads of a coroutine like this:

    Code (CSharp):
    1.     IEnumerator SomeCoroutine(string str, int val)
    2. // and this
    3.     IEnumerator SomeCoroutine(int val, string str)
    They would have the exact same fields and the compiler seems to first list reference types followed by value types. The general order seems to be those 5 sections in that order:
    1. internal statemachine state (
      <>1__state
      and
      <>2__current
      )
    2. reference type arguments (have the same name as the original arguments)
    3. potential
      <>4__this
    4. value type arguments (have the same name as the original arguments)
    5. other internally used variables (usually
      <NAME>5__POS
      )
    So the exact signature can not be reconstructed, though you could see what argument types the generator had and try to match them up by type.

    Note that in the case of an IEnumerator instance that is based on a generator method, the method name is in the angle brackets of the internal class / object name. So the generated class is named
    <SomeCoroutine>d__5
    . So you could extract the method name from the class name of the object.

    As I said I would not recommend such an approach. Your usecase is still not clear.

    If your question was about how it would work when you use an System.Action, it depends on two things:

    • If a generic version of
      System.Action<>
      is used, you would specify the signature in the Action itself, that's the point of the generic version. You can only assign that specific method / delegate that matches this signature.
    • If the non generic
      System.Action
      is used and you may assign a closure to the delegate, the actual parameters would be stored in another compiler generated closure object that wraps and captures the passed variables that are shared by closures that access the same instance.
    Note as I said above an IEnumerator does not have to be compiler generated. You are free to create your own class like that for example:

    Code (CSharp):
    1. public class MyCoroutineObj : IEnumerator
    2. {
    3.     private int i;
    4.     public object Current => null;
    5.     public bool MoveNext()
    6.     {
    7.         if (i < 10)
    8.         {
    9.             Debug.Log("Running " + Time.frameCount);
    10.             i++
    11.         }
    12.         return i <10;
    13.     }
    14.     public void Reset() { }
    15. }
    16.  
    That would be a more effective coroutine object that does the same as

    Code (CSharp):
    1. IEnumerator MyCoroutine()
    2. {
    3.     for(int i = 0; i < 10;i++)
    4.     {
    5.         Debug.Log("Running " + Time.frameCount);
    6.         yield return null;
    7.     }
    8. }
    9.  
    So this
    StartCoroutine(new MyCoroutineObj())
    and this
    StartCoroutine(MyCoroutine())
    would produce the same behaviour though the exact implementation of the object is different.
     
  11. Danixadem

    Danixadem

    Joined:
    Jul 31, 2018
    Posts:
    16
    First of all, thanks again for your response. I checked out your "coroutine crash course". It was helpful.

    Here is the question I wrote for chatGPT. I hope it clears out the ambiguity:
    In a Unity project coded in C#, I have a constructor (named Cons) that accepts an IEnumerator object (named Met). The Met object is a member method of another class other than the Cons-class. And that Met passed into Cons will be an IEnumerator-returning method (not an instance of an IEnumerator type object and not a delegate such as Action or Func<>). In the project, there are many overridden versions of Met with different numbers and types of parameters. Until runtime, Cons doesn't know which version of Met is passed into the Cons. But Cons must accept all overridden versions of Met. In runtime, Cons must get the parameter values and type of Met's parameters, and then make some processes on them.

    By gathering information from other sources. I managed to get the original name of the method (coroutine) that's passed as a parameter, the type data of the class which contains that coroutine class, and the parameter types of that coroutine method passed into the constructor. Even though I also tried to use AOP frameworks such as Castle and PostSharp; still couldn't get the parameter values of the IEnumerator method (coroutine) which is passed as a parameter into the ctor.
     
  12. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,003
    I don't think you have understood anything I explained a month ago, right? You do not pass a "method" into your constructor, you do actually pass an object into your constructor. IEnumerator is an interface type and only objects / classes can implement interfaces. As I said, when you call your coroutine method it returns an object that implements the IEnumerator interface. You will pass that object to your constructor, not your method.

    I also explained why it's impossible to actually derive the original signature of your coroutine that created the statemachine object because the way the compiler orders the fields in that compiler generated class. I gave two example coroutines with the same arguments but different order. Those would produce the exact same fields, so you can not distinguish them. The information you're looking for simply does not exist. It's like a method that takes two int arguments and returns another int like this:

    Code (CSharp):
    1. int MyMethod(int a, int b)
    2. {
    3.     return a + 5 * b;
    4. }
    When you call that method with the arguments 5 and 2, it will return 15. However just based on that 15 you can not determine what arguments were passed into the method. If could be (0,3), (5,2), (10,1), (-5,4) or (20,-1). They all return the value 15. With the coroutines it's similar. A generator method takes arguments and generates / creates an instance of a class that has been created by the compiler based on what your original method did. This object has no direct relation to the method it came from.

    Your whole premise is based on something that may work for very specific cases but not in general. That's your final answer. :)
     
  13. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,003
    But you're still don't get the point I made. To illustrate it have a look at this example:

    Code (CSharp):
    1. public class ClassA
    2. {
    3.     public object SomeMethod(int a, int b, string c)
    4.     {
    5.         return new ClassB(){b = b, a = a, c = c};
    6.     }
    7. }
    8.  
    9. public class ClassB
    10. {
    11.     public string c;
    12.     public int a;
    13.     public int b;
    14. }
    15.  
    16. // [ ... ]
    17.  
    18. ClassA obj = new ClassA();
    19. AnotherMethod(obj.SomeMethod(42,5,"Hello world"));
    This is an example of what you're facing, just without the IEnumerator interface. The method SomeMethod of ClassA simply creates an instance of ClassB. ClassB has fields which reflect the arguments of that method SomeMethod, but is otherwise completely unrelated to ClassA or the method SomeMethod. What you see inside AnotherMethod is JUST the instance of ClassB. It's just a reference to that class instance that is passed into the method. As we already discussed, knowing how the compiler magic turns your iterator method into an actual class, we can determine the method name the class is based on and we could also figure out what arguments were originally passed to the method based on the fields of that class. Like I already said, this analysis is a pure implementation detail of the compiler and different compilers could produce different code. Also we can not determin the order of the arguments since the order of the fields do not reflect the order of the arguments.

    You want to extract information that simply isn't there. Just like examining an iPhone doesn't tell you what the employee that was responsible for creating this iPhone instance had for breakfast at the day the phone was made. This information simply does not exist in that iPhone. Of course going to Apple, to figure out who was responsible for this particular phone, tracking down the employee and asking him what he had for breakfast would work (assuming you actually get that information from apple / the employee). The same is true for our case. Analysing the actual call chain to see where that instance actually was made and analysing the actual CIL code where you called the method in question would tell you everything you need to know. However that's not really possible because there's no record where it came from and even if you could follow the traces back the call stack, it's possible that you loose track at some point. Also all this would be code analysis which is exactly what you did not wanted. You only wanted to reverse engineer the method and method signature just based on the object that the method produced.

    Your original post had point 3:
    This is already fuzzy because your coroutine method IS invoked and it creates an instance of the statemachine which is returned. Yes you do not "start" the coroutine since you do not pass it to Unity via the StartCoroutine method. However your method has been invoked before your constructed is even called. If you actually want to analyse what method is used, you have to pass the actual generator method as a delegate which may be invoked later. But you explicitly excluded this in your point 2.

    So I repeat my final statement: What you want to do is not possible to pull off reliably without decompiling the actual IL code of all potential methods to see which one is creating this particular class instance that was passed into your constructor.

    I don't want to start a fight. You're the one asking for help. Not sure what you're intended by throwing around terms like aspect oriented programming. You should stick to the actual topic of your question. Since you're an advanced C# developer, it should be enough to mention: just use reflection to analyse the target class and analyse the IL code that you get from GetMethodBody (maybe have a look at the MethodBodyReader) or use the Cecil library :)
     
    Nefisto likes this.