Are generic operator overloads allowed in .NET 4?

2019-06-24 19:49发布

问题:

I am assuming "No", but I cannot find conclusive proof on Google to back this assumption. Using keywords of 'vb.net "generic operator overload"' yields exactly 1 result, and removing 'overload' gives more, but no direct statement to the issue.

My thinking is given an abstract class, it'd be great to be able to implement a generic operator overload that a derived class can use in such a case when said operator overload has to return a New copy of the derived class, yet the code for each overload is the same. If that makes any sense.

This touches back to my previous questions on my custom Enum class and overloading the bitwise operators (And, Or, Not, & Xor), but, this particular thought was prompted by a mere curiosity of "Can it be done?".

Here's what one of my custom enums basically look like:
The parent, EBase is nothing special, just hosting common Name and Value properties, plus two shared operators, op_Equality and op_Inequality.

Friend NotInheritable Class EExample
    Inherits EBase

    Private Sub New()
    End Sub

    Friend Shared Function GetValue(ByVal Name As String) As Enums
        Dim tmpOffset As Int32 = Array.IndexOf(_Names, Name)
        Return If(HasContent(Name), If(tmpOffset <> -1, Values(tmpOffset), Nothing), Nothing)
    End Function


    ' Num of Enums defined.
    Friend Shared ReadOnly MaxEnums As Int32 = 5

    ' String literals.
    Private Shared ReadOnly _Names As String() = New String() _
        {"one_adam", "two_boy", "three_charles", "four_david", "five_edward"}

    ' Enums.
    Friend Shared ReadOnly OneA As New Enums(_Names(0), 1)
    Friend Shared ReadOnly TwoB As New Enums(_Names(1), 2)
    Friend Shared ReadOnly ThreeC As New Enums(_Names(2), 4)
    Friend Shared ReadOnly FourD As New Enums(_Names(3), 8)
    Friend Shared ReadOnly FiveE As New Enums(_Names(4), 16)


    ' Enum Values Array.
    Friend Shared ReadOnly Values As Enums() = New Enums() _
        {OneA, TwoB, ThreeC, FourD, FiveE}


    Friend NotInheritable Class Enums
        Inherits EBase

        Private Sub New()
        End Sub

        Friend Sub New(ByVal Name As String, ByVal Value As Int32)
            MyBase.Name = Name
            MyBase.Value = Value
        End Sub
    End Class
End Class

Here's how the things are used:

Dim Foo As EExample.Enums
Foo = EExample.TwoB
Debug.Print(Foo.Name)

will print two_boy

Now, given that, if I want to do the following:

Dim Foo as EExample.Enums
Foo = EExample.OneA Or EExample.FiveE

I have to define an operator overload for Or inside the EExample.Enums definition. How would this operator overload look?

Public Shared Operator Or(ByVal lhOp As Enums, ByVal rhOp As Enums) As Enums
    Return New Enums(String.Concat(lhOp.Name, "|"c, rhOp.Name),
                     lhOp.Value Or rhOp.Value, True)
End Operator

I have to return a new EEXample.Enums object containing the Bitwise-Or'ed Value property of the parent EExample enums. For the name, I just concatenate the Name properties together with a pipe character until I think of something better.

Assume I have 20 enum classes similar to EExample. I have to duplicate all that operator overload code for each definition even though in the IDE, it looks the exact same. In IL, however, each overload is specific to the containing parent enum class:

.method public specialname static class MyAssembly.EExample/Enums 
        op_BitwiseOr(class MyAssembly.EExample/Enums lhOp,
                     class MyAssembly.EExample/Enums rhOp) cil managed
{ ... }

But! A generic operator overload would solve this problem if defined in EBase!

Friend Interface IEnums
    Property Name As String
    Property Value As Int32
End Interface

Public Shared Operator Or(Of T As IEnums)(ByVal lhOp As T, ByVal rhOp As T) As T
    Return New T(String.Concat(lhOp.Name, "|"c, rhOp.Name),
                 lhOp.Value Or rhOp.Value, True)
End Operator

Then (in theory anyways), calling EExample.OneA Or EExample.FiveE would work because the compiler would know to call the generic operator overload from EBase, know that EExample.Enums matches the IEnums interface constraint, and automatically supply T.

That or I'm just swimming up a certain creek here without a paddle and over-analyzing things. But it's an interesting thought, no? What is StackOverflow's consensus? Do I need to lay off the Spice a little bit?

PS: I know that, in the last example, Return New T( ... ) is invalid, but I can't think of a proper syntax that would articulate the basic idea.

回答1:

According to what I can see in the language specification, generic operators are not allowed. Section 9.8 says

The type of at least one of the operands or the return value must be the type that contains the operator.

and later when it describes the declaration syntax makes no accounting for a generic specifier as methods do in section 9.2.1.



回答2:

Found a "workable" solution myself.

For the top-level EBase, I exposed the interface (IEnumBase) as a Friend, then created generic methods in EBase to handle the overload operators:

Protected Shared Function _
op_BitwiseOr(Of T As {IEnumBase, Class})(ByVal lhOp As T, ByVal rhOp As T, ByVal RetEnum As T) As T
    RetEnum.Name = String.Concat(lhOp.Name, "|"c, rhOp.Name)
    RetEnum.Value = (lhOp.Value Or rhOp.Value)

    Return RetEnum
End Function

The trick here, is the generic method simply returns RetEnum back to the caller. In the derived Enums (i.e., EExample), I have:

Public Shared Shadows Operator Or(ByVal lhOp As Enums, ByVal rhOp As Enums) As Enums
    Return EBase.op_BitwiseOr(lhOp, rhOp, New Enums)
End Operator

This allows me to keep the bulkier code defined once in EBase, and not replicated each time in my many derived enum classes. Those enum classes simply call on the parent's implementation and use generics to pass-in their sub-defined Enums implementation!

Yeah, not groundbreaking. I could do better, but this works well enough for my needs and doesn't over-inflate the codebase too much. It also reduces code duplication and technically makes maintenance easier, IMHO.

Still leaving Gideon Engelberth's answer as the accepted answer, however. My question initially asked if overloaded operators could be genericized, and he found the snippet on MSDN that says they can't.