I don't really think this should change since it would be a big performance hit and you sort of need to abuse implementation details to do this. Maybe it should be documented somewhere though?
function Test-EnumMutation {
param([Reflection.BindingFlags] $Flags)
end {
$Flags.value__ = 30
}
}
$myFlags = [Reflection.BindingFlags]::CreateInstance
$myFlags
Test-EnumMutation -Flags $myFlags
$myFlags
CreateInstance
CreateInstance
CreateInstance
DeclaredOnly, Instance, Static, Public
Name Value
---- -----
PSVersion 7.1.0-preview.3
PSEdition Core
GitCommitId 7.1.0-preview.3
OS Microsoft Windows 10.0.18363
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
This may be a candidate for documentation in the docs wiki, at least initially.
Honestly even if the "documentation" is just closing this issue as wontfix
that's probably enough. I just wanted it recorded somewhere.
Are we able to quantify the performance hit? 馃
It bothers me just a tad that the copy semantics are so flawed. 馃槄
Are we able to quantify the performance hit? 馃
Tbh considering most actually immutable structs are copied and no one has noticed... 馃し
But changing the value of an enum via it's underlying field is not supported. Technically, the field can be named whatever the compiler wants.
I think PowerShell is doing the right thing because enums are supposed to be treated like constants, even if they're technically mutable structs.
Are enums treated like other value types (copied on assignment) in C#?
enum's are basically treated the exact same as their underlying type.
Take this sharp lab for instance:
public static void M() {
H(DayOfWeek.Friday);
}
public static void H(DayOfWeek dow) {
}
Translates to this in IL:
// Push an int32 constant of the value 5 onto the stack
IL_0000: ldc.i4.5
// Call the static method, pulling arguments from values currently pushed on the stack
IL_0001: call void C::H(valuetype [System.Private.CoreLib]System.DayOfWeek)
IL_0006: ret
A struct with a single field of int
is indistinguishable in memory from an int
(until boxed). The C# compiler makes full use of that.
To add to that (correct me if I'm wrong, @SeeminglyScience):
System.Int32
is the _default_ type _underlying_ - but (of necessity) not _derived_ from - concrete System.Enum
types, but it is possible to use a different underlying type (which should always be an _integer_ type) using _reflection_, according to the docs.
To learn what integer type underlies a given enum type, call .GetTypeCode()
on any of its values; e.g.:
PS> [System.PlatformID]::Unix.GetTypeCode()
Int32
@mklement0 you don't need reflection to make an enum of a different underlying type; they use inheritance-style syntax to define that in C#:
enum MyEnum : long
{
blah
}
And at some point during 6.x we got a similar syntax added to PS as well:
enum Test : Int64 {
blah
}
The note in the docs about reflection is stating it's possible to use a non-integer type to create an enum, but it is not recommended and only available via reflection.
EDIT:
The other way to get the underlying type out from an enum is [enum]::GetUnderlyingType([enumtype])
I appreciate the clarification, @vexx32 - I've hidden my previous comment; let me just re-state the part about determining the underlying type:
To learn what specific (integer) type underlies a given enum type, as an alternative to, e.g., [enum]::GetUnderlyingType([System.PlatformID])
, you can call .GetTypeCode()
on any of its values; e.g.:
# Returns a [System.TypeCode] enum value
PS> [System.PlatformID]::Unix.GetTypeCode()
Int32
System.Int32
is the _default_ type _underlying_ -
There isn't a true default as far as the runtime is concerned, but yeah that's the default for most compilers.
but (of necessity) not _derived_ from - concrete
System.Enum
types
The struct still derives from Enum
. My comment about how it appears in memory doesn't just apply to enums, it applies to all structs (that aren't boxed). Take this struct for example:
public readonly struct MyWrapper
{
public readonly int Value;
public MyWrapper(int value) => Value = value;
}
public static class Show
{
public static void Example()
{
var wrapper = new MyWrapper(10);
var value = 10;
}
}
In the above example, if you examined the byte value of both wrapper
and value
they would both look like 0xA, 0x0, 0x0, 0x0
. If MyWrapper
was a class, the byte value would be a memory address to a location on the heap. If you were to follow that memory address, you would not only get the byte value of the field Value
, but you'd also get a pointer to the method table for the class, the object header, etc.
Structs have none of that, there's no identity information, there's no method table, no object header, and it's all stored on the stack. Until it's boxed of course.
but it is possible to use a different underlying type (which should always be an _integer_ type)
Yeah, any built-in integer type. Though no compiler will let you use the native variants afaik.
Requirements for enums are defined in EMCA 335 搂I.8.5.2.
To learn what integer type underlies a given enum type, call
.GetTypeCode()
on any of its values; e.g.:
Echo what @vexx32 said. Also I usually go with [PlatformID].GetEnumUnderlyingType()
Thanks, @SeeminglyScience, that's helpful.
The struct still derives from Enum
Indeed, but my point was that the enum doesn't - and cannot - derive _from the underlying (integer) type_.
Also I usually go with
[PlatformID].GetEnumUnderlyingType()
Makes sense - good simplification.
Most helpful comment
@mklement0 you don't need reflection to make an enum of a different underlying type; they use inheritance-style syntax to define that in C#:
And at some point during 6.x we got a similar syntax added to PS as well:
The note in the docs about reflection is stating it's possible to use a non-integer type to create an enum, but it is not recommended and only available via reflection.
EDIT:
The other way to get the underlying type out from an enum is
[enum]::GetUnderlyingType([enumtype])