Search Unity

C# : Pass a parameter to a method by reference?

Discussion in 'Scripting' started by CFprime, May 28, 2015.

  1. CFprime

    CFprime

    Joined:
    Feb 6, 2015
    Posts:
    27
    How can I do something similar to the following.

    I have a Function that modifys an int. For simplicity sake, lets say it generates a random number.

    static int GenRand()
    {
    Random random = new Random();
    return( random.Next(0, 100));
    }

    Now instead of returning a random number I want to pass a property of an object. For example Go.transform.position.x or Go.light.intensity.

    (I'm aware the following does not work)

    static void GenRand(ref int PropertyToChange)
    {
    Random random = new Random();
    PropertyToChange = random.Next(0, 100));
    }

    GenRand(ref Go.transform.position.x);

    --------------------

    My actual need is far more complex, but I'm looking for a general way to pass a arbitrary property to an function and have it modified by the result.

    In other languages I would use a pointer(can't to my knowledge in Unity), or by reference.

    Any thoughts?
     
  2. SubZeroGaming

    SubZeroGaming

    Joined:
    Mar 4, 2013
    Posts:
    1,008
    why can't you have a


    GameObject modify(Gameobject obj)
    {
    modify obj and return it
    }
     
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I believe the reason that does not work is that the function expects an int but you are trying to pass a float. That wouldn't work even if you were using pointers.

    Also, please use code tags.
     
  4. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    I too ran into this trouble with properties recently. I'm not sure, but I suspect you can't modify properties by reference like that because code can get executed in the get/set functions. Maybe somebody knows a way to do it (maybe SubZeroGaming's idea would work? I don't know.).
     
  5. Hikiko66

    Hikiko66

    Joined:
    May 5, 2013
    Posts:
    1,304
    Also you can't assign to position.x directly, you have to create a whole new vector3.
     
  6. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Oh, yes, you won't be able to pass a property by reference because properties are not variables, they're actually methods.

    But I believe you should be able to pass a field by reference as long as the types match.
     
  7. CFprime

    CFprime

    Joined:
    Feb 6, 2015
    Posts:
    27

    I need to pass an arbitrary parameter on an object. Sometimes I want to modify the x position sometime a light's intensity...
     
  8. CFprime

    CFprime

    Joined:
    Feb 6, 2015
    Posts:
    27
    This sounds close...how do I pass a field?
     
  9. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    Just like you're doing in the original code where you use the "ref" keyword. Using "ref" modifies the variable that was passed in like you want.
     
  10. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Uh...basically in the way you were trying to do it, you just can't do it in your particular example because it wouldn't do what you're expecting so the compiler stops you.

    I believe you can do this:
    Code (CSharp):
    1. public class Foo {
    2.     public int x;
    3. }
    4.  
    5. public static void modifyInt(ref int toModify) {
    6.     toModify += 17;
    7. }
    8.  
    9. Foo foo = new Foo();
    10. foo.x = 5;
    11. modifyInt(ref foo.x);
    12. // foo.x now has the value 22
    The problem with your example is that transform.position is a property. It looks like a variable, but behind the scenes it's actually two methods: one method that returns a Vector3 representing the current position, and another method that accepts a Vector3 as an argument and sets the current position to that vector.

    So you can do this:
    Code (CSharp):
    1. Vector3 myVector = transform.position;  // behind the scenes, this calls a method that returns your current position as a Vector3
    2. myVector.x = 5;
    3. transform.position = myVector;  // behind the scenes, this calls a method that sets your current position based on a Vector3
    But you can't do this:
    Code (CSharp):
    1. transform.position.x = 5;
    If the compiler let you execute the line of code above, what that would actually mean is "create a temporary Vector3 that contains my current position, then change the X coordinate of that temporary vector to 5." Which wouldn't actually do anything, since you aren't using the temporary Vector3 for anything after that.

    So you aren't going to be able to pass transform.position.x by reference to a function (or get a pointer to it) for the same reason--transform.position is not a variable, it's actually a function that returns a temporary value.
     
    Last edited: May 29, 2015
    Todd-Wasson and ThermalFusion like this.
  11. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    You could always get whatever you want from the object if you pass a GameObject in. I don't see how 'ref' is going to help you, because you seem to want to be able to pass in different types of references - lights, transforms, etc. You can only pass in one type directly.

    But you can pass in a GameObject, which will have whatever component you want, and then you can access exactly what you want using GetComponent.

    However, I'm not really sure what you're trying to do or how important performance is for this situation. Depending on what you're doing, there are probably other ways to handle it as well.

    Also, you should be able to use pointers in C# just like in C++ by wrapping code in:

    unsafe
    {

    }

    (Assuming it works in Unity - I haven't actually tried it yet). But it's generally not recommended to do that unless you really, really must.
     
  12. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    "unsafe" works in Unity Pro. I've tried it.
     
  13. CFprime

    CFprime

    Joined:
    Feb 6, 2015
    Posts:
    27
    We are getting warm ;) Thank you all for the feedback so far.

    A slightly more specific description from me might help target this...

    The first example still holds, but was simplified. I have a tweening coroutine. I am passing it a start value and end value and the target to be modified. I want it to be generic so I can pass it a light's intensity, or a objects position basically anything that is a property and a float.

    Is this possible? To have a coroutine modify a property without that property being hard coded, like passing the gameObject and specifying the property in the function.

    I understand the Method vs Variable situation...How can I work within its restrictions?
     
  14. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Well, you could make your coroutine's argument be a delegate that sets the value of the thing you want to adjust. Something like:

    Code (CSharp):
    1. public delegate void FloatSetter(float newValue);
    2.  
    3. public IEnumerator MyCoroutine(FloatSetter set) {
    4.     for (int i = 0; i < 1000; ++i) {
    5.         set(i);
    6.         yield return WaitForSeconds(1);
    7.     }
    8. }
    9.  
    10. static void SetMyPositionX(float newX) {
    11.     Vector3 currentPosition = transform.position;
    12.     currentPosition.x = newX;
    13.     transform.position = currentPosition;
    14. }
    15.  
    16. // Call the coroutine with an pre-defined function
    17. StartCoroutine(MyCoroutine(SetMyPositionX));
    18.  
    19. // We could also define a function on-the-fly to reference whatever variable we want
    20. float myFloat;
    21. StartCoroutine(MyCoroutine(
    22.     (x) => myFloat = x;
    23. ));
    Other options depend on exactly what you're trying to accomplish.

    If the purpose of the coroutine is to perform an expensive calculation and then set the value of the variable once at the very end, then instead of the coroutine you may want to use an asynchronous call that returns the result of the computation and then set the variable yourself based on the return value. One way you might make an asynchronous call for the computation is described here.

    On the other hand, if the coroutine is going to be updating the variable continuously based on the general state of your game, you might consider replacing the coroutine with an object that publishes an event with the calculated value, and then adding listeners that can respond to that event in whatever way you like.
     
  15. CFprime

    CFprime

    Joined:
    Feb 6, 2015
    Posts:
    27

    Wonderful answer!

    Thank you all for feedback.
     
  16. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Umm... passing the "property" supplies the value. You'd have to use Reflection to actually get and pass the method that a property resolves to. So, that's not accurate.

    Yes and that's the route I would take. Just as a little background, the reason you can't do exactly what you were originally trying to do is that some items are immutable and some are value types that can't be directly modified. So for instance, you can't say:

    gameObject.transform.position.x = 5;

    Because that gives you a copy of the transform not a reference to the original. You'd have to say:

    Code (csharp):
    1.  
    2. var v = new Vector3(5, gameObject.transform.position.y, gameObject.transform.position.z);
    3. gameObject.transform.position = v;
    4.  
    That's why the delegate approach is more elegant as it allows you to handle these things gracefully as well as be more extensible.
     
  17. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Yes. So that's fine if you want to pas the argument by value. But if you are trying to pass the argument by reference it won't work.
     
  18. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Coroutines make passing in by reference more complicated. You can't use ref with IEnumerator, as there is no guareentee the parameters will actually exist.

    I'd suggest passing in a delegate or lambda function.

    But if you really wanted to go crazy you could use reflection.
     
  19. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    What Microsoft does which only solves part of the problem, is in the Entity Framework when dealing with Stored Procedures, it takes in a generic class (ParameterReference<T> basically) so it wraps the value type in a reference type.