0

I have written a custom extension method for setting/unsetting an Enum-Flag, which is based on some StackOverflow answers. The code basically looks as follows:

  Public Sub SetFlag(Of T As {Structure})(ByRef storage As T, value As T)
            EnsureTypeIsEnum(Of T)()
            Dim underlyingType As Type = System.Enum.GetUnderlyingType(storage.GetType())

            If (underlyingType Is GetType(UInt64)) Then
                Dim this = Convert.ToUInt64(storage)
                Dim flag = Convert.ToUInt64(value)

                storage = DirectCast(System.Enum.ToObject(GetType(T), (this Or flag)), T)
            Else
                Dim this = Convert.ToInt64(storage)
                Dim flag = Convert.ToInt64(value)
                Dim result = DirectCast((this Or flag), Object)

                storage = DirectCast(System.Enum.ToObject(GetType(T), (this Or flag)), T)
            End If
        End Sub

I am unsure whether I have to check if the value is signed or unsigned. When not specifying a certain type for an Enum, it is per default signed. Is there any good reason to specify an Enum as unsigned integer?

I tried to get an answer by looking at the .NET source code. The Enum.HasFlag-method does not perform this check. It always casts the value to ulong. I can't imagine that it is secure to do that. Are there any pitfalls?

 public Boolean HasFlag(Enum flag) {
            if (!this.GetType().IsEquivalentTo(flag.GetType())) {
                throw new ArgumentException(Environment.GetResourceString("Argument_EnumTypeDoesNotMatch", flag.GetType(), this.GetType())); 
            }

            ulong uFlag = ToUInt64(flag.GetValue()); 
            ulong uThis = ToUInt64(GetValue());
            return ((uThis & uFlag) == uFlag); 
        }

UPDATE:

I found out that the Enum-class silently converts all values to an UInt64. It also converts negative numbers very well without throwing an OverflowException, thus generating exactly the expected value.

internal static ulong ToUInt64(Object value) 
        {
            // Helper function to silently convert the value to UInt64 from the other base types for enum without throwing an exception.
            // This is need since the Convert functions do overflow checks.
            TypeCode typeCode = Convert.GetTypeCode(value); 
            ulong result;

            switch(typeCode) 
            {
                case TypeCode.SByte: 
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                    result = (UInt64)Convert.ToInt64(value, CultureInfo.InvariantCulture); 
                    break;

                case TypeCode.Byte: 
                case TypeCode.UInt16:
                case TypeCode.UInt32: 
                case TypeCode.UInt64:
                    result = Convert.ToUInt64(value, CultureInfo.InvariantCulture);
                    break;

                default:
                // All unsigned types will be directly cast 
                    Contract.Assert(false, "Invalid Object type in ToUInt64"); 
                    throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_UnknownEnumType"));
            } 
            return result;
        }
Dennis Kassel
  • 2,726
  • 4
  • 19
  • 30
  • I'm not sure what you're asking specifically. You seem to be asking multiple questions which are not directly related. – Steven Doggart Jul 20 '15 at 09:51
  • Basically my question is, if it is necessary to check if the value is signed or unsigned, because the .NET framework seems to not performing this check, but always casts the value to UInt64. – Dennis Kassel Jul 20 '15 at 10:03

1 Answers1

1
Enum Test As ULong
    Zero
    One
    Alot = &H8000000000000000UL
End Enum

That's an example of a troublemaker. If you don't treat UInt64 specially then SetFlag() will bomb with an OverflowException when you try:

Dim v As Test
SetFlag(v, Test.Alot)

Convert.ToInt64() is not happy about values larger than Int64.MaxValue.


Using only ToUint64() does not work either:

Enum Test
    MinusOne = -1
    Zero
    One
End Enum

Bombs with:

Dim v As Test
SetFlag(v, Test.MinusOne)

So no, you can't simplify this code without breaking it.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thank you for your answer. Indeed, there is way. The .NET Reference Code simply does a Convertion to UInt64. I assume, that it does no overflow checks, since otherwise it would throw an OverflowException. I had searched for a way to cast an Integer without overflow check in VB.NET without completely disabling it. In this thread is a code which does the desired cast: http://stackoverflow.com/questions/31436730/are-there-utility-methods-for-performing-unsafe-arithmetic-in-vb-net/31438176#31438176 – Dennis Kassel Jul 21 '15 at 08:58
  • That code cannot work, it relies on having Long to deal with a possible overflow for Integer. There is no larger type than Int64. Converting an Int64 with UInt64 without danger of an OverflowException requires an "re-interpret" cast. That has to be done with a union in VB.NET, I posted an example in [this answer](http://stackoverflow.com/a/4656890/17034). Make the second field of the Caster structure an ULong. Hard to see how you'd be ahead. – Hans Passant Jul 21 '15 at 09:07
  • But why does it then work in the HasFlag-method of the Enum class? It does nothing special but converts the value silently without throwing an exception. Above there is some code I've copied from the HasFlag-method. The responsible statement is: ulong result = (UInt64)Convert.ToInt64(value, CultureInfo.InvariantCulture); – Dennis Kassel Jul 23 '15 at 08:17
  • Enum.HasFlag was written in C# and was compiled with overflow checking disabled. You are using VB.NET, overflow checking is normally enabled. You can turn it off. You are asking questions in comments that should really be proper Q+A so other programmers can benefit from it. Please close this question and use the Ask Question button. – Hans Passant Jul 23 '15 at 08:26
  • Yes, HasFlag does not perform an overflow check. Anyway, a special reinterpreting of the values does not happen. Thus, we only do need some equivalent method in VB.NET. I finally could find the time to try out the code you can find in the following post. It works just fine. But perhaps I will ask a further question about whether it is legal to convert it that way anyway. Here you can find the post: http://stackoverflow.com/questions/31436730/are-there-utility-methods-for-performing-unsafe-arithmetic-in-vb-net/31438176#31438176 – Dennis Kassel Jul 24 '15 at 08:28