Can't reflect on Private Methods

2019-09-02 08:06发布

问题:

So I created this thread: Invoking Private / Protected Methods Via Reflection From The Same Object Instance (or Base)

And we got the problem fixed save for private methods. As this may not be the same issue I thought it may be best to post a different question with the full source. It is still a work in progress but it is functional.

The base class:

Public MustInherit Class BaseTransactionalSaveManager : Implements ITransactionalSaveManager

    '---- Public Properties & Backing Fields ----'

    Public Property FormDataIsValid As Boolean Implements ITransactionalSaveManager.FormDataIsValid

    '---- Private Properties & Backing Fields ----'

    Protected Property Stages As Collections.Generic.List(Of String)
    Protected Property StageCausedRollback As Containers.GenericNamedValuePair(Of String, Boolean)
    Protected Property CurrentStage As Integer

    '---- Event Declarations & Associated Methods ----'

    Public Event TransactionCancelled As EventHandler(Of CustomEventArgs.GenericSingleEventArgs(Of String)) Implements ITransactionalSaveManager.TransactionCancelled

    Public Event TransactionCompleted As EventHandler(Of CustomEventArgs.GenericSingleEventArgs(Of String)) Implements ITransactionalSaveManager.TransactionCompleted

    Public Event TransactionStagePassed As EventHandler(Of CustomEventArgs.GenericSingleEventArgs(Of String)) Implements ITransactionalSaveManager.TransactionStagePassed

    Protected Overridable Sub OnTransactionCancelled(e As CustomEventArgs.GenericSingleEventArgs(Of String)) Implements ITransactionalSaveManager.OnTransactionCancelled

        RaiseEvent TransactionCancelled(Me, e)

    End Sub

    Protected Overridable Sub OnTransactionCompleted(e As CustomEventArgs.GenericSingleEventArgs(Of String)) Implements ITransactionalSaveManager.OnTransactionCompleted

        RaiseEvent TransactionCompleted(Me, e)

    End Sub

    Protected Overridable Sub OnTransactionStagePassed(e As CustomEventArgs.GenericSingleEventArgs(Of String)) Implements ITransactionalSaveManager.OnTransactionStagePassed

        RaiseEvent TransactionStagePassed(Me, e)

    End Sub

    '---- Constructors ----'

    Public Sub New()

        Stages = New Collections.Generic.List(Of String)
        SetStages()
        CurrentStage = 0

        StageCausedRollback = New Containers.GenericNamedValuePair(Of String, Boolean)
        FormDataIsValid = True

    End Sub

    '---- Public Methods ----'

    Public Sub ProcessStage() Implements ITransactionalSaveManager.ProcessStage

        ' Use stage to fire the correct method.

        Me.GetType.InvokeMember(Stages(CurrentStage),
                                Reflection.BindingFlags.InvokeMethod Or
                                Reflection.BindingFlags.NonPublic Or
                                Reflection.BindingFlags.Public Or
                                Reflection.BindingFlags.Instance,
                                Type.DefaultBinder, Me, Nothing)

        ' Determine if the stage should cause a rollback.

        If Not StageCausedRollback.Value Then

            RollBackTransaction(StageCausedRollback.Name)
            Exit Sub

        End If

        ' Check if this stage is the last one.

        If Stages(CurrentStage) = Stages.Last Then

            OnTransactionCompleted(New CustomEventArgs.GenericSingleEventArgs(Of String)(Stages(CurrentStage)))

        Else

            OnTransactionStagePassed(New CustomEventArgs.GenericSingleEventArgs(Of String)(Stages(CurrentStage)))

        End If

    End Sub

    Public Overridable Function TryCancelTransaction() As Boolean Implements ITransactionalSaveManager.TryCancelTransaction

        OnTransactionCancelled(New CustomEventArgs.GenericSingleEventArgs(Of String)(""))
        Return True

    End Function

    '--- Protected & Overridable Methods ----'

    Protected Overridable Sub SetStages()

        Me.Stages.Add(MethodNameToString(AddressOf Me.ConfirmFormDataIsValid))

    End Sub

    Protected Overridable Sub RollBackTransaction(stageThatCauseRollback As String)

        OnTransactionCancelled(New CustomEventArgs.GenericSingleEventArgs(Of String)(stageThatCauseRollback))

    End Sub

    Protected Function MethodNameToString(addressOfMethod As Action) As String

        Return addressOfMethod.Method.Name

    End Function

    Private Sub ConfirmFormDataIsValid()

        StageCausedRollback.Name = MethodNameToString(AddressOf ConfirmFormDataIsValid)
        StageCausedRollback.Value = If(FormDataIsValid, True, False)

    End Sub

End Class

So this class is being inherited by a (so far) empty child class and ProcessStage is being called. Notice that ConfirmFormDataIsValid() sub is private. If you run this it will not find this method. If I change it to protected however it works fine.

Am I missing something?

回答1:

Instead of Me.GetType.InvokeMember (first line of ProcessStage), you need to call Me.GetType.BaseType.InvokeMember

You won't see private members in subclasses, even with BindingFlags.NonPublic.

Obviously that solution will be a bit fragile, as it depends how many levels of subclasses you have as to whether you will see the method in the BaseType or not. You might need to loop up the chain of classes until you reach a BaseType of BaseTransactionalSaveManager and then find the method.

Hope that helps.



回答2:

This really doesn't answer your question but hopefully gives you some inspiration to use the Template Method Pattern.

First off, you're violating DRY by having the methods and creating a list of those method names. Now if you do a method rename, you have to change it in 2 places.

By using the Template Method Pattern, you provide an abstract method where the child class defines all the methods they want called and in the right order (replacing the list they're providing now). You lose all the overhead of reflecting to those methods and the developers are no longer restricted to no method parameters.

Also, if there's a good way to do something without reflection, it's usually a much cleaner and easier-to-understand solution.

-- CW because it's really a very long comment and doesn't answer the question at hand.