Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EnumBuilder.UnderlyingSystemType != EnumBuilder.CreateType().UnderlyingSystemType #110924

Open
DanielBThayer opened this issue Dec 24, 2024 · 7 comments
Labels
area-System.Reflection.Emit breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. bug help wanted [up-for-grabs] Good issue for external contributors
Milestone

Comments

@DanielBThayer
Copy link

Description

When creating an enumeration via the DefineEnum on the ModuleBuilder, the emitted type's UnderlyingSystemType is set to the type being emitted (creating a circular reference path). It should be set to the provided type.

Reproduction Steps

ModuleBuilder _moduleBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.RunAndCollect).DefineDynamicModule("Test");

EnumBuilder builder = _moduleBuilder.DefineEnum("E_Test", TypeAttributes.Public, typeof(int));

builder.DefineLiteral("Low", 0);
builder.DefineLiteral("High", 1);

Type type = builder.CreateType();

if (type.UnderlyingSystemType == type)
{
throw new Exception("Circular reference");
}
else if (type.UnderlyingSystemType != builder.UnderlyingSystemType)
{
throw new Exception("This should never throw");
}

Expected behavior

Emitted type has the underlying type set to the type defined in the DefineEnum call ("Circular reference" exception will be thrown)

Actual behavior

Emitted type has the underlying type set to itself

Regression?

No response

Known Workarounds

No response

Configuration

.Net version: .Net 8.0.11
OS: Windows 11, latest updates applied
Arch: x64

System.Reflection: 4.3.0

Other information

No response

@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Dec 24, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-reflection-emit
See info in area-owners.md if you want to be subscribed.

@elgonzo
Copy link

elgonzo commented Dec 25, 2024

What value do you expect for type.UnderlyingSystemType? If it is int, then type.UnderlyingSystemType is the wrong place to look for it. If you want to get the value type underlying an enum, you have to use the type.GetEnumUnderlyingType() or Enum.GetUnderlyingType(Type) method.

@huoyaoyuan
Copy link
Member

UnderlyingSystemType is hardcoded to self for any runtime provided type:

public override Type UnderlyingSystemType => this;

It's used for creating modifiers on type, not underlying numeric type of enums.

public override Type UnderlyingSystemType => _unmodifiedType;

@jkotas
Copy link
Member

jkotas commented Dec 25, 2024

Transient Reflection.Emit has somewhat broken implementation of UnderlyingSystemType that returns GetEnumUnderlyingType.

For example:

using System.Reflection;
using System.Reflection.Emit;

ModuleBuilder _moduleBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Test"), AssemblyBuilderAccess.RunAndCollect).DefineDynamicModule("Test");

EnumBuilder builder = _moduleBuilder.DefineEnum("E_Test", TypeAttributes.Public, typeof(int));

Console.WriteLine(builder.UnderlyingSystemType);

Prints System.Int32.

This issue is requesting to replicate this behavior in runtime type universe. I agree with other commenters that it does not make sense. Instead, we may want to fix the transient Reflection.Emit implementation.

Note that the persisted Reflection.Emit behaves as expected. There is a mismatch between transient and persisted Reflection.Emit behaviors.

For example:

using System.Reflection;
using System.Reflection.Emit;

ModuleBuilder _moduleBuilder = new PersistedAssemblyBuilder(new AssemblyName("Test"), typeof(object).Assembly).DefineDynamicModule("Test");

EnumBuilder builder = _moduleBuilder.DefineEnum("E_Test", TypeAttributes.Public, typeof(int));

Console.WriteLine(builder.UnderlyingSystemType);

Prints Type: E_Test.

@steveharter
Copy link
Member

This issue is requesting to replicate this behavior in runtime type universe. I agree with other commenters that it does not make sense. Instead, we may want to fix the transient Reflection.Emit implementation.

Agree - we should change the behavior of AssemblyBuilder to match the runtime behavior (to return this) which PersistedAssemblyBuilder also matches and which was discussed at #106375.

@steveharter steveharter added bug breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. and removed untriaged New issue has not been triaged by the area owner labels Dec 26, 2024
@steveharter steveharter added this to the 10.0.0 milestone Dec 26, 2024
@dotnet-policy-service dotnet-policy-service bot added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label Dec 26, 2024
Copy link
Contributor

Added needs-breaking-change-doc-created label because this issue has the breaking-change label.

  1. Create and link to this issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.

Tagging @dotnet/compat for awareness of the breaking change.

@steveharter steveharter added help wanted [up-for-grabs] Good issue for external contributors and removed needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet labels Dec 26, 2024
@DanielBThayer
Copy link
Author

What is the point of the UnderlyingSystemType, if it points to type itself? Then there is the documentation mismatch with the implementation. The documentation seems to say it will refer to the system type which represents how the enumeration is stored (e.g. UInt16, Int16, UInt32, Int32, UInt64, Int64, etc). I am referring to the documentation at:

For reference, this issue was causing problems later on in my program when I was attempting to Marshal a byte array to a generic parameter type (e.g. T) of the method call (signature of the method is "public static T CastByBytes(byte[] data)"). My work around was to evaluate the generic parameter type's type (i.e. using IsEnum, IsArray, typeof(bool), etc) to handle different cases causing issues with the code (e.g. bool is 4 bytes on x64 systems, but the source of the byte array has them as 1 byte).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Reflection.Emit breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. bug help wanted [up-for-grabs] Good issue for external contributors
Projects
None yet
Development

No branches or pull requests

5 participants