最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - How can I check if a generic enum has value? - Stack Overflow

programmeradmin5浏览0评论

In my C# application, I have an abstract class that is a wrapper around UnitsNet library to group closely related properties (such as a numeric value and unit for that value that user has chosen), to limit the available units of measurement, and also to handle complex inputs from the user (such as multiple number inputs, such as "5, 8, 10:1:20"). Here is the base class and one of the classes that extends it:

public abstract class PropertyBase<TQuantity, TUnit> : where TQuantity : IQuantity where TUnit : Enum
{
    public string InputString { get; set; }

    public TUnit? SelectedUnit
    {
        get => _selectedUnit;
        set
        {
            var oldUnit = _selectedUnit;
            _selectedUnit = value;
            if (oldUnit != null) //This check doesn't work
            {
                //This code should only be run if oldUnit is not null or 0
                ConvertInputString(oldUnit, value);
            }
        }
    }
    
    private TUnit? _selectedUnit;
    protected abstract TUnit[] AvailableUnits { get; }
    protected abstract TQuantity FromValue(double value, TUnit unit);
    protected abstract double As(TQuantity quantity, TUnit unit);
    
    private void ConvertInputString(TUnit oldUnit, TUnit newUnit)
    {
        //Parses and converts values in InputString
    }
    protected List<double> ParseInputValues(string input)
    {
        //...
    }
}

public class InputPropertyAngles : PropertyBase<Angle, AngleUnit>, IInputProperty 
{
    public InputPropertyAngle()
    {
        SelectedUnit = AngleUnit.Degree;
    }
    public override double[] ValuesInSi
    {
        get
        {
            var values = ParseInputValues(InputString).ToArray();
            return values.Select(value => Angle.From(value, SelectedUnit).Degrees).ToArray();
        }
    }
    
    protected override AngleUnit[] AvailableUnits => [AngleUnit.Degree, AngleUnit.Radian];
    protected override Angle FromValue(double value, AngleUnit unit) => Angle.From(value, unit);
    protected override double As(Angle quantity, AngleUnit unit) => quantity.As(unit);
}

Note that AngleUnit is an Enum that comes from UnitsNet library.

The idea is that when the InputPropertyAngle is instanciated, and SelectedUnit is set to default unit in the constructor, the ConvertInputString should not be run. This is done by checking if oldUnit is null (if (oldUnit != null)). However, since oldUnit is TUnit (generic Enum), it cannot be nullable. During runtime, it assumes a value of 0.

Luckily, in UnitsNet library, in AngleUnit Enum, 0 doesn't represent any meaningful value. So I thought I could replace that check with if (oldUnit != 0), but compiler says Cannot apply operator '!=' to operands of type 'TUnit' and 'int'. Unfortunately I cannot constrain TUnit to anything more specific than Enum, because AngleUnit, LengthUnit, MassUnit and other Enums that come from UnitsNet don't have any common interface (and as I understand Enums can't have that at all).

What is the most elegant way to solve this to make that null check possible?

In my C# application, I have an abstract class that is a wrapper around UnitsNet library to group closely related properties (such as a numeric value and unit for that value that user has chosen), to limit the available units of measurement, and also to handle complex inputs from the user (such as multiple number inputs, such as "5, 8, 10:1:20"). Here is the base class and one of the classes that extends it:

public abstract class PropertyBase<TQuantity, TUnit> : where TQuantity : IQuantity where TUnit : Enum
{
    public string InputString { get; set; }

    public TUnit? SelectedUnit
    {
        get => _selectedUnit;
        set
        {
            var oldUnit = _selectedUnit;
            _selectedUnit = value;
            if (oldUnit != null) //This check doesn't work
            {
                //This code should only be run if oldUnit is not null or 0
                ConvertInputString(oldUnit, value);
            }
        }
    }
    
    private TUnit? _selectedUnit;
    protected abstract TUnit[] AvailableUnits { get; }
    protected abstract TQuantity FromValue(double value, TUnit unit);
    protected abstract double As(TQuantity quantity, TUnit unit);
    
    private void ConvertInputString(TUnit oldUnit, TUnit newUnit)
    {
        //Parses and converts values in InputString
    }
    protected List<double> ParseInputValues(string input)
    {
        //...
    }
}

public class InputPropertyAngles : PropertyBase<Angle, AngleUnit>, IInputProperty 
{
    public InputPropertyAngle()
    {
        SelectedUnit = AngleUnit.Degree;
    }
    public override double[] ValuesInSi
    {
        get
        {
            var values = ParseInputValues(InputString).ToArray();
            return values.Select(value => Angle.From(value, SelectedUnit).Degrees).ToArray();
        }
    }
    
    protected override AngleUnit[] AvailableUnits => [AngleUnit.Degree, AngleUnit.Radian];
    protected override Angle FromValue(double value, AngleUnit unit) => Angle.From(value, unit);
    protected override double As(Angle quantity, AngleUnit unit) => quantity.As(unit);
}

Note that AngleUnit is an Enum that comes from UnitsNet library.

The idea is that when the InputPropertyAngle is instanciated, and SelectedUnit is set to default unit in the constructor, the ConvertInputString should not be run. This is done by checking if oldUnit is null (if (oldUnit != null)). However, since oldUnit is TUnit (generic Enum), it cannot be nullable. During runtime, it assumes a value of 0.

Luckily, in UnitsNet library, in AngleUnit Enum, 0 doesn't represent any meaningful value. So I thought I could replace that check with if (oldUnit != 0), but compiler says Cannot apply operator '!=' to operands of type 'TUnit' and 'int'. Unfortunately I cannot constrain TUnit to anything more specific than Enum, because AngleUnit, LengthUnit, MassUnit and other Enums that come from UnitsNet don't have any common interface (and as I understand Enums can't have that at all).

What is the most elegant way to solve this to make that null check possible?

Share Improve this question asked Jan 17 at 20:07 K.S.K.S. 775 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

You can restrict your TUnit to struct (since enums are value types) which will allow you to work with nullable values:

public abstract class PropertyBase<TQuantity, TUnit> where TQuantity : IQuantity where TUnit : struct, Enum
{
    public TUnit? SelectedUnit
    {
        get => _selectedUnit;
        set
        {
            var oldUnit = _selectedUnit;
            _selectedUnit = value;
            // TODO: rewrite logic taking in account that 
            // value.Value will throw if value is null
            if (oldUnit is not null && value is not null) 
            {
                ConvertInputString(oldUnit.Value, value.Value);
            }
        }
    }

    // ...
}

or remove nullability and use default (I "moved" it to a virtual property so inheritor can redefine the Default if needed):

public abstract class PropertyBase<TQuantity, TUnit> where TQuantity : IQuantity where TUnit : struct, Enum
{
    protected virtual TUnit Default => default;

    public TUnit SelectedUnit
    {
        get => _selectedUnit;
        set
        {
            var oldUnit = _selectedUnit;
            _selectedUnit = value;
            if (!oldUnit.Equals(Default)) 
            {
                ConvertInputString(oldUnit, value);
            }
        }
    }

    // ...
}

See also "The oddities of the Enum constraint" section of the Dissecting new generic constraints in C# 7.3 article:

There is one very interesting caveat with the Enum constraint: the constraint does not imply but itself that the T is a struct. Moreover, you can actually combine the Enum constraint with the class constraint.

The combination where TEnum: class, Enum makes no sense and the only reasonable way to use the Enum constraint is to use it with the struct constraint: where TEnum: struct, Enum.

发布评论

评论列表(0)

  1. 暂无评论