Arraycollection is being passed into function by value instead of by reference in flex 3
I want to set arrayCollection #2 = to arrayCollection #1 via a function in flex 3. I pass both array collections to a function and set arrayCollection #2 = arrayCollection #1. However, it seem开发者_开发知识库s to not be passing arrayCollection #2 by reference because after the function call, arrayCollection #2 has not been changed. My understanding is that it should be passed by reference and work, am I doing something wrong? Below is the code:
var AC1:ArrayCollection = new ArrayCollection;
var AC1.addItem(someObject);
var AC2:ArrayCollection = new ArrayCollection;
setAC2(AC1,AC2);
// AC2 is not set to AC1 after the function
private function setAC2(_ac1:ArrayCollection, _ac2:ArrayCollection):void
{
_ac2 = _ac1;
}
Please see Evaluation Strategy.
AS uses "pass by object" / "pass by object-sharing". That is, the "object" is passed (not a copy, clone or duplicate) and any modifications to the object are shared.
However, the assignment _ac2 = _ac1
only changes the value of the [function's local] parameter variable and will have no affect on any variables during the function invocation. The only thing passed in are the values ("objects") which result from the evaluation of the variables (or any arbitrary expression) used in the function invocation.
This is because, as stated above, the strategy used is "pass by object" and not (as the documentation states "pass by reference", which really means, "pass by value [of the reference]" or just ... "pass by object"). That is, the term "pass by reference" is actually misused and hence, confusing. (It is misused in a number of languages and documentation. It is an uphill battle trying to getting to a common meaning.)
If it were really "pass by reference" then assigning a new value to _ac2
would propagate out. (Before posing a comment saying how AS is "pass by reference", please see the link at top and consider that "pass by reference" covers the case of C#'s out/ref
, VB's ByRef
, TSQL's output
and C++'s (reference) &
-- these notions are not in AS, Javascript, or Java). However, as correctly noted in the original post (and additional self-reply), it is not the case -- conclusion: AS does not support "pass by reference"; furthermore, the documentation (confusingly) uses the term "pass by reference" to mean "pass by object" / "pass by object-sharing".
There are several ways that the change can be propagate out, ordered by order of (my) preference:
Return the new applicable value:
AC2 = doSomeTransformation(AC1)
. This generally the cleanest. Avoid side-effects and surprising code. Multiple values can be returned if wrapped in an object (or array) as appropriate.Use a closure:
doSomeTranformation(AC1, function (newValue) { AC2 = newValue })
where doSomeTransformation might look like:function doSomeTransformation(_ac1, finished) { ...; finished(_ac1) }
. I generally only use this when the callback "runs in context" of the function itself or when writing code in a CPS-style.Mutate an object (AS is "pass by object", after all). This is very icky, but it will work.
var blah = {AC2: null}; doSomeTransformation(ac1, blah); ...; laterOn(blah.AC2)
where doSomeTransformation might look look likefunction doSomeTransformation(_ac1, b) { ...; b.AC2 = _ac1; }
. Not recommended in general.
Happy coding.
Applicable excerpts, from Evaluation Strategy:
"call by reference": (my main argument for "call by reference" being used incorrectly is that it already has a well-defined meaning; the overloaded term adopted by some languages such as AS and Python just adds confusion)
In call-by-reference evaluation (also referred to as pass-by-reference), a function receives an implicit reference to a variable used as argument, rather than a copy of its value. This typically means that the function can modify the variable used as argument- something that will be seen by its caller.
"call by object" / "call by object-sharing": (but pay heed to where it acknowledges the inconsistency/localization of these terms; the term "call by reference" is often misused to imply these semantics and "call by value [of the reference]" is used in some contexts to also mean the same thing)
The semantics of call-by-sharing differ from call-by-reference in that assignments to function arguments within the function aren't visible to the caller (unlike by-reference semantics), so e.g. if a variable was passed, it is not possible to simulate an assignment on that variable in the caller's scope. However since the function has access to the same object as the caller (no copy is made), mutations to those objects, if the objects are mutable, within the function are visible to the caller, which may appear to differ from call-by-value semantics.
THe function arguments in ActionScript pass by value, not by reference. It is absolutely the same as in Java. You can read in details here.
The problem I am seeing is
var AC1.addItem(someObject);
Try adding the item within a function.
var AC1:ArrayCollection = new ArrayCollection;
var AC2:ArrayCollection = new ArrayCollection;
addItemToArrayCollection( AC1 );
setAC2(AC1,AC2);
// AC2 should be pointing to the ArrayCollection that AC1 is pointing to.
private function setAC2(_ac1:ArrayCollection, _ac2:ArrayCollection):void
{
_ac2 = _ac1;
}
private function addItemToArrayCollection( arrayCollection:ArrayCollection ):void
{
arrayCollection.addItem( someObject );
}
You can add a breakpoint in after the the assignment and see that AC2 should have the same Object as AC1.
I believe that @Constantiner is on the right track, but I think his explanation is lacking detail; so I'm going to try to explain with a bit more depth; as best I understand it. Ya'll can correct me if I'm wrong.
As stated in the docs:
In ActionScript 3.0, all arguments are passed by reference, because all values are stored as objects. However, objects that belong to the primitive data types, which includes Boolean, Number, int, uint, and String, have special operators that make them behave as if they were passed by value.
So, an ArrayCollection is definitely an object, and not a primitive type, so it should be passed by reference and act like it was passed by reference. But, what is your reference variable to the ArrayCollection. Conceptually it just a pointer to some memory space that contains the actual Collection data. Here is my attempt at some ASCII art:
|---|
ac1(variable)--> | | (ActualArrayCollection1)
|---|
|---|
ac2(variable)--> | | (ActualArrayCollection2)
|---|
to repeat, ac1variable is a pointer to some memory space. ac2variable is a pointer to some different memory space. When you pass one of them into a method as an argument it is passed by reference. So, inside the method, you have something like this:
ac1(variable)--> |---|
ac1(argument)--> | | (ActualArrayCollection1)
|---|
ac2(variable)--> |---|
ac2(argument)--> | | (ActualArrayCollection2)
|---|
So both ac1variable and ac1argument point at the same memory space; because they each contain the same pointer value. However, ac1variable and ac1argument are actually holding different memory spaces. They are not the same.
When the method runs this line:
_ac2 = _ac1;
You get something like this:
ac1(variable)--> |---|
ac1(argument)--> | | (ActualArrayCollection1)
ac2(argument)--> |---|
ac2(variable)--> |---|
| | (ActualArrayCollection2)
|---|
When the method's execution ends, the two arguments go away, and the original pointer variables remain unchanged. If you want to do a direct assignment like this inside a method, you can access the global variable using the this keyword. This should do it:
this._ac2 = _ac1;
Of course, that can defeat the purpose of encapsulation inside a method if you're accessing class level variables.
I'm sure an expert on compiler design and such things will eat this for breakfast and spit it up. I hope my ASCII art is consistent across multiple browsers / machines / OSes / etc..
精彩评论