So, I tried looking up the answer to this question, and found the generally available answer is that PowerShell passes parameters by value. These generally accepted solutio
Note: The following also applies to assigning one variable to another: $b = $a
...
* makes $b
reference the very same object that $a
does if $a
's value is an instance of a reference type,
* makes $b
receive an independent copy of $a
's value if the latter is an instance of a value type.
PowerShell uses by-(variable)-value passing by default; that is, the content of a variable is passed, not a reference to the variable itself.
[ref]
-typed parameter (akin to ref
parameters in C#). However, note that this technique is rarely necessary in PowerShell.Whether that content is a copy of what the caller sees or a reference to the very same object depends on the data type of the content:
If the content happens to be an instance of a .NET reference type - as [pscustomobject]
is - that content is an object reference, and the callee can therefore potentially modify that object, by virtue of seeing the very same object as the caller.
.Clone()
method, but note that it is up to the implementing type whether to perform shallow or deep cloning[1]; it is for that reason that use of this interface is discouraged; in practice, types that do implement it typically perform shallow cloning, notably arrays, array lists (System.Collections.ArrayList
) and hashtables (but note that an [ordered]
hashtable (System.Collections.Specialized.OrderedDictionary
) doesn't implement ICloneable
at all..psobject.Copy()
on instances of type [pscustomobject]
to create a shallow copy. (Do not use this method on objects of any other type, where it will effectively be a no-op.) Similarly, individual .NET types may implement custom cloning methods.If, by contrast, that content is an instance of a .NET value type - e.g., [int]
- or a
string[2], an independent copy of that instance is passed.
This distinction is fundamental to .NET, not specific to PowerShell; it is also how arguments are passed in C#, for instance.
To determine whether a given variable's value is an instance of a value type or a reference type, use something like the following:
1, (Get-Date), (Get-Item /) | # sample values
foreach {
'{0} is of type {1}; is it a value type? {2}' -f $_,
$_.GetType(),
$_.GetType().IsValueType
}
You'll see something like:
1 is of type System.Int32; is it a value type? True
4/30/2020 12:37:01 PM is of type System.DateTime; is it a value type? True
/ is of type System.IO.DirectoryInfo; is it a value type? False
If you look up the documentation for a given .NET type, say System.DateTime, the inheritance information will start with Object -> ValueType
for value types; in C# terms, a value type is either a struct
or an enum
, whereas a reference type is a class
.
There are two unrelated concepts at play here, and the fact that they both use the terms (by-)value and (by-)reference can get confusing:
By-(variable)-value vs. by-(variable)-reference parameter-passing is a data-holder (variable) concept:
Reference types vs. value types is purely a data concept:
[1] Shallow cloning means that property values that are reference-type instances are copied as-is - as references - which means that the clone's property value again references the very same object as the original. Deep cloning means that such property values are cloned themselves, recursively. Deep cloning is expensive and isn't always possible.
[2] A string ([string]
) is technically also an instance of a reference type, but, as an exception, it is treated like a value type; see this answer for the rationale behind this exception.
[3] Another way of thinking about it: a reference (pointer) to the location where the variable stores its value is passed. This allows the callee to not only access the variable's value, but also to assign a (new) value.