vblang
vblang copied to clipboard
<Flags> Enum operators
<Flags> Enum Operators
Operating with <Flags>Enums can be a point a pain, so I've been thinking for quite a while on have the following operators. That expand the capabilities of the dictionary lookup operator, eg does the variable have the flag IsShared set? We are looking up the flag(s).
source ! flaganameandsource !( flagsEnumExpression )- Are all of the matching flags set in source.
source !? flagnameandsource !?( flagsEnumExpression )- Are any of the matching flags set is source.
source !+ flagnameandsource !+ ( flagsEnumExpression )- Set the matching flags in source.
source !- flagnameandsource !- ( flagsEnumExpression )- Clear the matching flags in source.
The the converted to the calls or implementation of the corresponding methods. This also allows compile-time constant folding to happen. As well compile-time validation of the flagname.
Function AllSet(Of T As <Flags>Enum)(source As T, matching As T) As Boolean
Return (source And matching) = matching
End Function
Function AnySet(Of T As <Flags>Enum)(source As T, matching As T) As Boolean
Return (source And matching) <> 0
End Function
Function SetFlags(Of T As <Flags>Enum)(source As T, matching As T) As T
Return (source Or matching)
End Function
Function ClearFlags(Of T As <Flags>Enum)(source As T, matching As T) As T
Return (source And (Not matching))
End Function
Implementation
The expression are to represented via a FlagsEnumExpression that supports the above operators,
an implementation detail there will be a "effective direct cast conversion" from the textual member (eg the flag's name) to a FlagsEnumExpression (eg flags value).
<Flags>Enum -> FlagsEnumExpression
Note we'll require some smarts in the compiler to differeniate between the type character singlevariable! ' Of Type Single and that of these operators.
Too symbolic. Try use keywords to express them to be a VB style.
@VBAndCs any suggestions for keywords?
AllSet, AnySet, SetFlags, and ClearFlags but I am not sure why you would not just use the extensions shown except maybe for the constant folding or performance.
@paul1956 The extension methods are applicable to any enum, not just those with a <Flags> attribute.
Until now, Enum can't be a type param constraint. This can work today:
Module Flags
<Extension>
Public Function [Set](e As [Enum], ParamArray flags() As [Enum]) As [Enum]
If flags Is Nothing OrElse flags.Count = 0 Then Return e
Dim result As [Enum]
For Each flag In flags
result = [Enum].Parse(e.GetType, Convert.ToInt32(e) Or Convert.ToInt32(flag))
Next
Return result
End Function
Public Function IsSet(e As [Enum], ParamArray flags() As [Enum]) As Boolean
If flags Is Nothing OrElse flags.Count = 0 Then Return e
Dim result As [Enum]
For Each flag In flags
result = [Enum].Parse(e.GetType, Convert.ToInt32(e) And Convert.ToInt32(flag))
Next
Return Convert.ToInt32(result) <> 0
End Function
End Module
But the problem is that I assumed is ToInt32 to be the Enum type. There is no easy solution to this except checking for all numeric types and writing a branch of code for each! I hope to have a new Flag type dedicated for this, that can do low level bitwise operation with pointers directly, to be fast and accurate.
In your example the parameters e and flags can contain values from different enums.
True. Needs type checking. This was a quick example.
Added the following to the Mercury.dll Completely strict, including type checking and working with the correct enum underlying type. note: only quickly tested.
<Runtime.CompilerServices.Extension>
Public Function SetFlags(e As [Enum], ParamArray flags() As [Enum]) As [Enum]
If flags Is Nothing OrElse flags.Length = 0 Then Return e
For Each flag In flags
If e.GetType <> flag.GetType Then Throw New ArgumentException("flag Is Not of the same type as the enum")
If Not e.HasFlag(flag) Then
e = DirectCast(CTypeDynamic(CTypeDynamic(Convert.ToInt64(e) + Convert.ToInt64(flag), e.GetType.GetEnumUnderlyingType), e.GetType), [Enum])
End If
Next
Return e
End Function
<Runtime.CompilerServices.Extension>
Public Function UnsetFlags(e As [Enum], ParamArray flags() As [Enum]) As [Enum]
If flags Is Nothing OrElse flags.Length = 0 Then Return e
For Each flag In flags
If e.GetType <> flag.GetType Then Throw New ArgumentException("flag Is Not of the same type as the enum")
If e.HasFlag(flag) Then
e = DirectCast(CTypeDynamic(CTypeDynamic(Convert.ToInt64(e) - Convert.ToInt64(flag), e.GetType.GetEnumUnderlyingType), e.GetType), [Enum])
End If
Next
Return e
End Function
<Runtime.CompilerServices.Extension>
Public Function AreFlagsSet(e As [Enum], ParamArray flags() As [Enum]) As Boolean
If flags Is Nothing OrElse flags.Length = 0 Then Return False
Dim result As Boolean = True
For Each flag In flags
result = result And e.HasFlag(flag)
Next
Return result
End Function
@
Nice, but this is not the perfect performance. I wrote this because it is the only way not the best way:
e = DirectCast(CTypeDynamic(CTypeDynamic(Convert.ToInt64(e) + Convert.ToInt64(flag), e.GetType.GetEnumUnderlyingType), e.GetType), [Enum])
It will be better if you provide a way to ease the enum operation. We need to deal with the exact type ot the enum, as Comvert.ToInt64 will return wrong results with other numeric types.
I imagine that should be an Enum(of T as Numeric) . One pain in generics in .NET is the missing Numeric constraint.
You should also create the overloaded operators for +-*/ And AndAlso Or OrElse Xor Not for the generic type Enum, so that we can just write:
If e.HasFlag(flag) Then
e = e - flag
End If
This is how a smart VB should be.
Agreed. But at this point I (just like you) have to work with the available functionality (we can not change and add all at once - as I do the Mercury.dll, I can add things like this to it).
I used the Int64 as that is the biggest possible type for an enum, to cast it (dynamic) to the correct type - otherwise a select case was needed. For now a working solution that will be improved in the future.
The true solution for this issue is to crate a Flag class. And to avoid generics and base class issues, each Flag class should be a full stand alone class, containing all what the flag needs. To make this practical, I created a flag generator app here: https://github.com/VBAndCs/VB-Flag-auto-generator
It is a Windows Forms app, that allows you to enter the name of the Flag and its members, then press the Generate button to auto-generate a class that represents this flags and its members, and many methods to do flag operations and set and unset flags.
The underline type of the flag is Integer, and the class contains CType operators to convert between them. This is a list of the members I added to the class:
Shared Fields:
NoneSet
AllSet
Shared Properties:
Flags
FlagNames
FlagValues
Instance Properties:
Name
OnFlags
OffFlags
Instance Methods:
ToString()
ToInteger
SetFlags
SetAllExcxept
UnsetFlags
UnsetAllExcxept
ToggleFlags
ToggleAllFlags()
AreAllSet
AreAllUnset
AreAnySet
AreAnyUnset
And this is a sample of an auto-generated Flag:
Class MyFlag
Public Shared ReadOnly X As new MyFlag("X", 1)
Public Shared ReadOnly Y As new MyFlag("Y", 2)
Public Shared ReadOnly Z As new MyFlag("Z", 4)
Public Shared ReadOnly NoneSet As MyFlag = 0
Public Shared ReadOnly AllSet As MyFlag = 7
Public Shared ReadOnly Property Flags As MyFlag() = {X, Y, Z}
Public Shared ReadOnly Property FlagNames As String() = {"X", "Y", "Z"}
Public Shared ReadOnly Property FlagValues As Integer() = {1, 2, 4}
Public ReadOnly Property Name As String
Public ReadOnly Property OnFlags As List(Of MyFlag)
Get
Dim lstFlags As New List(Of MyFlag)
For Each flag In Flags
If (Value And flag.Value) > 0 Then lstFlags.Add(flag)
Next
Return lstFlags
End Get
End Property
Public ReadOnly Property OffFlags As List(Of MyFlag)
Get
Dim lstFlags As New List(Of MyFlag)
For Each flag In Flags
If (Value And flag.Value) = 0 Then lstFlags.Add(flag)
Next
Return lstFlags
End Get
End Property
Dim Value As Integer
Private Sub New(value As Integer)
Me.Value = value
End Sub
Private Sub New(name As String, value As Integer)
_Name = name
Me.Value = value
End Sub
Public Shared Widening Operator CType(value As Integer) As MyFlag
Return New MyFlag(value)
End Operator
Public Shared Narrowing Operator CType(flag As MyFlag) As Integer
Return flag.Value
End Operator
Public Overrides Function ToString() As String
Dim sb As New Text.StringBuilder
For Each flag In Flags
If (Value And flag.Value) > 0 Then
If sb.Length > 0 Then sb.Append(" + ")
sb.Append(flag.Name)
End If
Next
Return sb.ToString
End Function
Public Function ToInteger() As Integer
Return Value
End Function
Public Function SetFlags(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return Value
Dim v = Value
For Each flag In flags
v = v Or flag.Value
Next
Return v
End Function
Public Function SetAllExcxept(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return Value
Dim v As Integer = 0
For Each flag In flags
v = v Or flag.Value
Next
Return Value And Not v
End Function
Public Function UnsetFlags(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return Value
Dim v = Value
For Each flag In flags
v = v And Not flag.Value
Next
Return v
End Function
Public Function UnsetAllExcxept(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return Value
Dim v As Integer = 0
For Each flag In flags
v = v Or flag.Value
Next
Return v
End Function
Public Function ToggleFlags(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return Value
Dim v = Value
For Each flag In flags
v = v Xor flag.Value
Next
Return v
End Function
Public Function ToggleAllFlags() As MyFlag
Return Value Xor 7
End Function
Public Function AreAllSet(ParamArray flags() As MyFlag) As Boolean
If flags Is Nothing OrElse flags.Length = 0 Then Return Value = 7
For Each flag In flags
If (Value And flag.Value) = 0 Then Return False
Next
Return True
End Function
Public Function AreAllUnset(ParamArray flags() As MyFlag) As Boolean
If flags Is Nothing OrElse flags.Length = 0 Then Return Value = 0
For Each flag In flags
If (Value And flag.Value) <> 0 Then Return False
Next
Return True
End Function
Public Function AreAnySet(ParamArray flags() As MyFlag) As Boolean
If flags Is Nothing OrElse flags.Length = 0 Then Return Value > 0
For Each flag In flags
If (Value And flag.Value) <> 0 Then Return True
Next
Return False
End Function
Public Function AreAnyUnset(ParamArray flags() As MyFlag) As Boolean
If flags Is Nothing OrElse flags.Length = 0 Then Return Value < 7
For Each flag In flags
If (Value And flag.Value) = 0 Then Return True
Next
Return False
End Function
End Class
and this is a sample top show how to use it:
Dim flag As MyFlag = MyFlag.X
flag = flag.SetFlags(MyFlag.Z)
Console.WriteLine(flag) ' X + Z
Console.WriteLine(flag.AreAllSet()) ' False
flag = flag.ToggleAllFlags()
Console.WriteLine(flag) ' Y
flag = flag.SetFlags(MyFlag.Z)
Console.WriteLine(flag) ' Y + Z
Console.WriteLine(flag.ToInteger) ' 6
Console.WriteLine(flag.AreAllSet(MyFlag.Y, MyFlag.Z)) ' True
To Do:
- Validate user inputs.
- Write tests for the FlagGenerator class.
- Crate a VS.NET extension to add a command to show the auto-generation window and add the generated file to the project.
@VBAndCs I think that Class MyFlag concept misses the point of what "enum flags" are about. For starters, MyFlag should be a value-type (i.e. Structure MyFlag) and not a reference-type. For another, MyFlag members seem to presume all of its constants are equal to some specific power of 2; however, that is not how enum flags are necessarily valued. For example, it is not unusual for a flag named "None" to exist which is set to zero; AreAllSet would fail on that.
Interpreting (or treating) an integer as a packed bits field is a very old-school programming trick. Sometimes it is necessary for interfacing with (manipulating) hardware registers (e.g. code for a device driver). Otherwise, it's merely a way to save on memory, and in some scenarios faster performance.
Enum already has the HasFlag method (https://docs.microsoft.com/en-us/dotnet/api/system.enum.hasflag). One can already do plenty with the bit-wise versions of Or, And, and Not. Just make useful extensions to set and clear bits as you may need them, since that is trivially easy to do. If you need to build-out a value from the power-of-2 constants, + is good enough (otherwise just use Or if you're paranoid). If you're careful to keep all "don't care" bits set to zero, then =0 (none set) and <>0 (any set) can be useful and fast.
If needs are somehow more sophisticated or whatever:
https://docs.microsoft.com/en-us/dotnet/api/system.collections.bitarray
https://docs.microsoft.com/en-us/dotnet/api/system.collections.specialized.bitvector32
https://stackoverflow.com/questions/14464/bit-fields-in-c-sharp
==== Side issue: It is annoying that the .NET generics system is nearly useless for value-types. However, the T4 Text Template can be of help - see T4Example.zip at #462.
For starters, MyFlag should be a value-type (i.e. Structure MyFlag) and not a reference-type.
Using structures is impossible here. The Falg class is immutable (all methods returns a new instance, andd the constructor is private, so the only way to get a new Flag is by starting with the pre-defined shared flags).
MyFlag members seem to presume all of its constants are equal to some specific power of 2; however, that is not how enum flags are necessarily valued. For example, it is not unusual for a flag named "None" to exist which is set to zero; AreAllSet would fail on that.
I already generates 2 additional fields named NoneSet (Name="None", Value= 0), and AllSet(Name= "All", Value= Max allowed Integer, witch is the sum of the flags).
Interpreting (or treating) an integer as a packed bits field is a very old-school programming trick. Sometimes it is necessary for interfacing with (manipulating) hardware registers (e.g. code for a device driver). Otherwise, it's merely a way to save on memory, and in some scenarios faster performance.
This is exactly what the flag class does, and I think you should give it a try to test it in action.
Finally, flagged enums are not enough (as the discussion in this issues shows), and it is always hard for beginners to use it without a good understanding of binary system, which doesn't fit the VB description, and is an overkill for commercial app developers.
Not that I made some changes in the auto-generated class to display the names of the set flags in the ToString method, andded some new methods, and Overloaded all the arithmetic operators, as VB ignores the fact that the flag can be casted to/from Integer and refused to use =, +, >…etc with it before overriding them! I will report this bad behavior in Roslyn. As I always said, VB Should be smarter than that. I even wonder if a class overloaded = and > why on earth it needs to overload >= unless of course it means something else > or = ?! VB should use her brain to do all what is necessary before crying out for an overloaded operator.
Of course there is already a discussion about enum improvements over in the run time forum.
Which might be a better place for discussions around improvements to enums, since they are framework, not VB constructs.
based on the work of Tyler Brinkley
VB Should be smarter than that. I even wonder if a class overloaded
= and >why on earth it needs to overload>=unless of course it means something else> or =?! VB should use her brain to do all what is necessary before crying out for an overloaded operator.
This limitation exists in C# as well. I presume it's the same as the reason why you have to implement both = and <> (or == and != in C#), if you're implementing one of them.
I like to think that the very smart people behind the design of Roslyn did actually think about this before deciding to do it this way.
If I was to make a list of things I wanted the compiler to do for me that it doesn't already do, this would be way down somewhere near the bottom of the list.
The creating of new classes that need to override >= and <= is such a small part of a developer's work, and has so little bearing on programmer productivity, that I can't see the value in compiler developers implementing code to make assumptions that may not be valid.
is a small part ....
Small indeed:
Public Shared Widening Operator CType(value As Integer) As MyFlag
Return New MyFlag(value)
End Operator
Public Shared Narrowing Operator CType(flag As MyFlag) As Integer
Return flag.Value
End Operator
Public Shared Operator +(flag As MyFlag, value As Integer) As Integer
Return flag.Value + value
End Operator
Public Shared Operator -(flag As MyFlag, value As Integer) As Integer
Return flag.Value - value
End Operator
Public Shared Operator *(flag As MyFlag, value As Integer) As Integer
Return flag.Value * value
End Operator
Public Shared Operator /(flag As MyFlag, value As Integer) As Integer
Return flag.Value / value
End Operator
Public Shared Operator ^(flag As MyFlag, value As Integer) As Long
Return flag.Value ^ value
End Operator
Public Shared Operator Or(flag As MyFlag, value As Integer) As MyFlag
Return New MyFlag(flag.Value Or value)
End Operator
Public Shared Operator And(flag As MyFlag, value As Integer) As MyFlag
Return New MyFlag(flag.Value And value)
End Operator
Public Shared Operator Xor(flag As MyFlag, value As Integer) As MyFlag
Return New MyFlag(flag.Value Xor value)
End Operator
Public Shared Operator Not(flag As MyFlag) As MyFlag
Return New MyFlag(Not flag.Value)
End Operator
Public Shared Operator IsTrue(flag As MyFlag) As Boolean
Return flag.Value > 0
End Operator
Public Shared Operator IsFalse(flag As MyFlag) As Boolean
Return flag.Value = 0
End Operator
Public Shared Operator =(flag As MyFlag, value As Integer) As Boolean
Return flag.Value = value
End Operator
Public Shared Operator <>(flag As MyFlag, value As Integer) As Boolean
Return flag.Value <> value
End Operator
Public Shared Operator >(flag As MyFlag, value As Integer) As Boolean
Return flag.Value > value
End Operator
Public Shared Operator <(flag As MyFlag, value As Integer) As Boolean
Return flag.Value < value
End Operator
Public Shared Operator >=(flag As MyFlag, value As Integer) As Boolean
Return flag.Value >= value
End Operator
Public Shared Operator <=(flag As MyFlag, value As Integer) As Boolean
Return flag.Value <= value
End Operator
Public Shared Operator +(flag1 As MyFlag, flag2 As MyFlag) As Integer
Return flag1.Value + flag2.Value
End Operator
Public Shared Operator -(flag1 As MyFlag, flag2 As MyFlag) As Integer
Return flag1.Value - flag2.Value
End Operator
Public Shared Operator *(flag1 As MyFlag, flag2 As MyFlag) As Integer
Return flag1.Value * flag2.Value
End Operator
Public Shared Operator /(flag1 As MyFlag, flag2 As MyFlag) As Double
Return flag1.Value / flag2.Value
End Operator
Public Shared Operator ^(flag1 As MyFlag, flag2 As MyFlag) As Long
Return flag1.Value ^ flag2.Value
End Operator
Public Shared Operator Or(flag1 As MyFlag, flag2 As MyFlag) As MyFlag
Return New MyFlag(flag1.Value Or flag2.Value)
End Operator
Public Shared Operator And(flag1 As MyFlag, flag2 As MyFlag) As MyFlag
Return New MyFlag(flag1.Value And flag2.Value)
End Operator
Public Shared Operator Xor(flag1 As MyFlag, flag2 As MyFlag) As MyFlag
Return New MyFlag(flag1.Value Xor flag2.Value)
End Operator
Public Shared Operator =(flag1 As MyFlag, flag2 As MyFlag) As Boolean
Return flag1.Value = flag2.Value
End Operator
Public Shared Operator <>(flag1 As MyFlag, flag2 As MyFlag) As Boolean
Return flag1.Value <> flag2.Value
End Operator
Public Shared Operator >(flag1 As MyFlag, flag2 As MyFlag) As Boolean
Return flag1.Value > flag2.Value
End Operator
Public Shared Operator <(flag1 As MyFlag, flag2 As MyFlag) As Boolean
Return flag1.Value < flag2.Value
End Operator
Public Shared Operator >=(flag1 As MyFlag, flag2 As MyFlag) As Boolean
Return flag1.Value >= flag2.Value
End Operator
Public Shared Operator <=(flag1 As MyFlag, flag2 As MyFlag) As Boolean
Return flag1.Value <= flag2.Value
End Operator
@VBAndCs
how many times are you going to write that?
and how long does it take you?
compared with how many classes you write, and how much time you spend developing your business application.
I've said similar elsewhere - the compiler should make:
90% easy 9% not hard 1% achievable, if hard
those % are based on how often a feature will be getting used by a programmer.
Your collection of operators for the 'MyFlag' class is about 10 minutes work. If you only have to do that once during a 100 hour project, then it falls into the 1% bracket, which the compiler should make achievable, which it does.
As far as I'm concerned, overloading operators is a very 'niche' thing to do. In 18 years of coding in .NET, I can count on one hand the number of times I've bothered to do it, and that was mostly out of academic interest, because it isn't actually necessary - there's nothing you can do with an operator that you can't already do with methods.
Since we're talking about Flags Enums, which are conceptually arrays of Boolean values, can you explain the point of the following non-Boolean operators when dealing with a Flags Enum? As far as I'm concerned, they make no sense:
+(Add)'(Subtract)*(Multiply)/(Divide)^(Raise to a power)>(Is greater than)<(Is less than)>=(Is greater than or equal to)<=(Is less than or equal to)
@pricerc I am covering my bases. This is an auto generated, for a general purpose use, so, someone can decide for some reason to use the Flag as an Integer and do some operation. These operators returns Integer , so, they doesn't alter the Flag logic. In fact the utility needs to offer some options to allow the user to check the methods and Operator categories he needs. Bit-wise operators are a must, but comparison maybe less important (not that bigger value means that more flags are set), and finally the arithmetic operators which is the useful. I need also to allow the user to change methods names. But all this needs time that I don't have currently. Note that I changed the underline Type from Integer to UInteger, to prevent negative values, and have a wider range. This is the optimun form of the generated flag I have so far:
Class MyFlag
Public Shared ReadOnly X As New MyFlag("X", 1)
Public Shared ReadOnly Y As New MyFlag("Y", 2)
Public Shared ReadOnly Z As New MyFlag("Z", 4)
Const MaxValue As UInteger = 7
Public Shared ReadOnly None As New MyFlag("None", 0)
Public Shared ReadOnly All As New MyFlag("All", MaxValue)
Private ReadOnly Value As UInteger
Private Sub New(value As UInteger)
Me.Value = If(value > MaxValue, value And MaxValue, value)
End Sub
Private Sub New(name As String, value As UInteger)
_Name = name
Me.Value = If(value > MaxValue, value And MaxValue, value)
End Sub
Public Shared ReadOnly Property Flags As MyFlag() = {X, Y, Z}
Public Shared ReadOnly Property FlagNames As String() = {"X", "Y", "Z"}
Public Shared ReadOnly Property FlagValues As UInteger() = {1, 2, 4}
Public ReadOnly Property Name As String
Public Property OnFlags As List(Of MyFlag)
Get
Dim lstFlags As New List(Of MyFlag)
For Each flag In Flags
If (Value And flag.Value) > 0 Then lstFlags.Add(flag)
Next
Return lstFlags
End Get
Set()
SetFlags(Value.ToArray())
End Set
End Property
Public Property OffFlags As List(Of MyFlag)
Get
Dim lstFlags As New List(Of MyFlag)
For Each flag In Flags
If (Value And flag.Value) = 0 Then lstFlags.Add(flag)
Next
Return lstFlags
End Get
Set()
UnsetFlags(Value.ToArray)
End Set
End Property
Default Public ReadOnly Property IsSet(flag As MyFlag) As Boolean
Get
Return (Value And flag.Value) > 0
End Get
End Property
Public Overrides Function ToString() As String
Return ToString("+")
End Function
Public Overloads Function ToString(Separator As String) As String
If Value = 0 Then Return None.Name
If Value = MaxValue Then Return All.Name
Dim sb As New Text.StringBuilder
For Each flag In Flags
If (Value And flag.Value) > 0 Then
If sb.Length > 0 Then sb.Append(Separator)
sb.Append(flag.Name)
End If
Next
Return sb.ToString
End Function
Public Function ToInteger() As UInteger
Return Value
End Function
Public Function SetFlag(flag As MyFlag) As MyFlag
Return New MyFlag(Value Or flag.Value)
End Function
Public Function SetFlags(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return New MyFlag(Value)
Dim v = Value
For Each flag In flags
v = v Or flag.Value
Next
Return New MyFlag(v)
End Function
Public Function SetAllExcxept(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return New MyFlag(Value)
Dim v = MaxValue
For Each flag In flags
v -= flag.Value
Next
Return New MyFlag(v)
End Function
Public Function UnsetFlag(flag As MyFlag) As MyFlag
Return New MyFlag(Value And Not flag.Value)
End Function
Public Function UnsetFlags(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return New MyFlag(Value)
Dim v = Value
For Each flag In flags
v = v And Not flag.Value
Next
Return New MyFlag(v)
End Function
Public Function UnsetAllExcxept(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return New MyFlag(Value)
Dim v As UInteger = 0
For Each flag In flags
v += flag.Value
Next
Return New MyFlag(v)
End Function
Public Function ToggleFlag(flag As MyFlag) As MyFlag
Return New MyFlag(Value Xor flag.Value)
End Function
Public Function ToggleFlags(ParamArray flags() As MyFlag) As MyFlag
If flags Is Nothing OrElse flags.Length = 0 Then Return New MyFlag(Value)
Dim v = Value
For Each flag In flags
v = v Xor flag.Value
Next
Return New MyFlag(v)
End Function
Public Function ToggleAllFlags() As MyFlag
Return New MyFlag(Value Xor 7)
End Function
Public Function AreAllSet(ParamArray flags() As MyFlag) As Boolean
If flags Is Nothing OrElse flags.Length = 0 Then Return Value = 7
For Each flag In flags
If (Value And flag.Value) = 0 Then Return False
Next
Return True
End Function
Public Function AreAllUnset(ParamArray flags() As MyFlag) As Boolean
If flags Is Nothing OrElse flags.Length = 0 Then Return Value = 0
For Each flag In flags
If (Value And flag.Value) <> 0 Then Return False
Next
Return True
End Function
Public Function AreAnySet(ParamArray flags() As MyFlag) As Boolean
If flags Is Nothing OrElse flags.Length = 0 Then Return Value > 0
For Each flag In flags
If (Value And flag.Value) <> 0 Then Return True
Next
Return False
End Function
Public Function AreAnyUnset(ParamArray flags() As MyFlag) As Boolean
If flags Is Nothing OrElse flags.Length = 0 Then Return Value < 7
For Each flag In flags
If (Value And flag.Value) = 0 Then Return True
Next
Return False
End Function
#Region "Operators"
' Contains all arithmetic and logical operators to work on Flags and Integers
' No need to publish them all here
#End Region
End Class
I am covering my bases. This is an auto generated, for a general purpose use, so, someone can decide for some reason to use the Flag as an Integer and do some operation.
Lovely. All of that to encourage the worst of "leaky abstractions".
https://en.wikipedia.org/wiki/Leaky_abstraction
https://stackoverflow.com/questions/3883006/meaning-of-leaky-abstraction
https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/
An Enum is simply a peculiar way to organize the namespace of presumably related integer constants. The <Flag> attribute changes the output of .ToString() and the functioning of .Parse/.TryParse. It also - to a point - communicates to humans the bit-field interpretation upon these specially named integer constants.
Even so, the main abstraction here is that of a bit-field. The fact that an integer is used as its basis doesn't mean we should make it too implicitly so.
I changed the + to set the sent flag, and - to clear it. Thee flag can be Flag object or a number. I also dropped the rest of the arithmetic operators, but kept the comparison ones, as it has a meaning regarding the flag as I described. So, the operator code size is not too much less. In fact if I used C#, it can drop significantly, as it is more cleaver in using operators than VB. This seems one of the reasons that made programmers leave VB to C#.