-
Notifications
You must be signed in to change notification settings - Fork 63
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
<Flags> Enum operators #520
Comments
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 |
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! |
In your example the parameters |
True. Needs type checking. This was a quick example. |
Added the following to the Mercury.dll
|
@ 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.
This is how a smart VB should be. |
Agreed. 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:
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:
|
@VBAndCs I think 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 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 ==== |
For starters, MyFlag should be a value-type (i.e. Structure MyFlag) and not a reference-type.
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).
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 |
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 |
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. |
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 |
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 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:
|
@pricerc 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 |
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 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. |
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 flagIsShared
set? We are looking up the flag(s).source ! flaganame
andsource !( flagsEnumExpression )
source !? flagname
andsource !?( flagsEnumExpression )
source !+ flagname
andsource !+ ( flagsEnumExpression )
source !- flagname
andsource !- ( flagsEnumExpression )
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.
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.The text was updated successfully, but these errors were encountered: