Skip to content

ShouldSerializeValue for PropertyDescriptor fails for values of underlying enum types #52116

Closed

Description

@KlausLoeffelmann

Description

The ShouldSerializeValue method of the PropertyDescriptor fails, when - in C# - a DefaultValue for a Property of an enum type is not defined with the enum type, but with it's underlying type. For example:

    internal class Foo : Component
    {
        [DefaultValue(0)]
        public TestEnum Bar { get; set; }
    }

    internal enum TestEnum
    {
        Value1 = 0,
        Value2 = 1
    }

This is even more dramatic in Visual Basic, where the compiler replaces the Enum type by its underlaying type by default, which makes properties of type enum, which have been defined in Visual Basic, never succeed in a ShouldSerializeValue call.

    <DefaultValue(TestEnum.Value1)>
    Public Property Bar As TestEnum

becomes automatically (IL translated to C#):

    [DefaultValue(0)]
    public TestEnum Bar
    {
        [CompilerGenerated]
        get
        {
            return _Bar;
        }
        [CompilerGenerated]
        set
        {
            _Bar = value;
        }
    }

Background:
When the DefaultValue property of a ReflectPropertyDescriptor is called, e.g. by calling ShouldSerializeProperty of PropertyDescriptor, and then the DefaultValue is of Type Enum but the value is given of the type of the underlying type, a NullReferenceException is thrown and the call fails.

Making this work and most likely serviced from .NET Core 3.1 on is, amongst other scenarios, critical for the Windows Forms Designer not to crash in those scenarios. (Which are not niche.)

Configuration

  • All .NET Core/.NET Versions.
  • Windows.
  • x86, x64, ARM
  • No.

Regression?

No.

Other information

The error might be here:

where toObject is called with _defaultValue instead of the local variable defaultValue.

(Thanks @DustinCampbell for helping tracking this down.
Also @merriemcgaw, @RussKie, @JeremyKuhne, @Pilchie for reference.)

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Apr 30, 2021
  2. merriemcgaw commented on Apr 30, 2021

    @merriemcgaw

    Please note that this is blocking a common scenario for WinForms devs, and now that usage of the designer is increasing it's going to generate a lot of noise as people port their apps. We should consider servicing 3.1 and 5 as they are the most common targets for porting Framework apps to.

  3. added this to the 6.0.0 milestone on May 6, 2021
  4. added
    blockingMarks issues that we want to fast track in order to unblock other important work
    and removed
    untriagedNew issue has not been triaged by the area owner
    on May 6, 2021
  5. merriemcgaw commented on Jul 14, 2021

    @merriemcgaw

    @maryamariyan can you let us know if there is any status update on this one? It is blocking a high priority scenario for the WinForms designer and we need it fixed in .NET 6, probably even serviced so customers building Custom Controls are able to use their default values as they did before.

    cc: @Pilchie

  6. safern commented on Jul 15, 2021

    @safern
    Member
  7. ericstj commented on Jul 15, 2021

    @ericstj
    Member

    use their default values as they did before.

    @merriemcgaw @KlausLoeffelmann this was not filed as a regression, did the same behavior exist on .NETFramwork? Why is this so important to fix now if it’s not a regression?

  8. DustinCampbell commented on Jul 15, 2021

    @DustinCampbell
    Member

    Yes, this is a regression from .NET Framework that occurred during some refactoring of ReflectPropertyDescriptor in .NET Core. The NetFx code looks like this:

    defaultValue = ((DefaultValueAttribute)a).Value;
    // Default values for enums are often stored as their underlying integer type:
    if (defaultValue != null && PropertyType.IsEnum && PropertyType.GetEnumUnderlyingType() == defaultValue.GetType()) {
        defaultValue = Enum.ToObject(PropertyType, defaultValue);
    }

    Essentially, the NetFx version uses a field called defaultValue throughout. In .NET Core, this was refactored to lift the field to a local variable. However, one of the field references was missed, resulting in a null-ref:

    // Default values for enums are often stored as their underlying integer type:
    object defaultValue = ((DefaultValueAttribute)a).Value;
    bool storedAsUnderlyingType = defaultValue != null && PropertyType.IsEnum && PropertyType.GetEnumUnderlyingType() == defaultValue.GetType();
    _defaultValue = storedAsUnderlyingType ?
        Enum.ToObject(PropertyType, _defaultValue) :
        defaultValue;

    The fix is simple. Change Enum.ToObject(PropertyType, _defaultValue) to Enum.ToObject(PropertyType, defaultValue).

    We didn't discover it until more recently because VB code is more likely to produce IL that triggers the exception, due to the fact that VB's implicit conversions and overload resolution is different from C#'s. There are more details about that here.

  9. ericstj commented on Jul 16, 2021

    @ericstj
    Member

    I see, it was regressed here between 1.1 and 2.0: dotnet/corefx@af3f34f#diff-4e980b410175c51d6330532288f63963ecd72513029c6954e5e862e7476e39c4R297. Seems like a simple refactoring bug (which we should have had tests to catch) cc @stephentoub

    Thanks for the great report, it's clear now. I'll put up a fix. For things like this in the future you're welcome to submit a PR. You've done a ton of leg-work to get this far already and it might have even been less work to submit a PR than file the issue. There's probably a lot more expertise in WinForms on this codebase too. Thanks again for the report and a fix will go in shortly. Let us know if you need this ported to P7 or servicing.

  10. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Jul 16, 2021
  11. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Jul 16, 2021
  12. KlausLoeffelmann commented on Aug 4, 2021

    @KlausLoeffelmann
    MemberAuthor

    @merriemcgaw - considering we are getting customer reports about this, should we consider servicing?

    dotnet/winforms#2748

  13. merriemcgaw commented on Aug 17, 2021

    @merriemcgaw

    If we're getting customer reports we should consider it. It doesn't seem to be a terribly edge case situation, but I'd leave the final call with @ericstj.

  14. ericstj commented on Aug 18, 2021

    @ericstj
    Member

    We don't proactively service and "getting customer reports" isn't sufficient for meeting the bug bar. Could you apply the bug bar? https://dev.azure.com/devdiv/DevDiv/_wiki/wikis/DevDiv.wiki/545/NET-Servicing?anchor=bug-bar

  15. ghost locked as resolved and limited conversation to collaborators on Sep 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.ComponentModelblockingMarks issues that we want to fast track in order to unblock other important work

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions