从 VB Class 实例获取 Field 对象,而不是 FieldInfo

Get a Field Object, not FieldInfo, from a VB Class Instance

我正在尝试遍历 class 中的对象(字段)并在每个对象上调用一个方法。每个对象都是不同的类型。这是父 class:

Public Class MySettings
    Public IdentifyByFacType As RadioButtonSetting

    Public WtrFacTypes As ListSetting
    Public OilFacTypes As ListSetting

    Public GroupByRef As CheckboxSetting
    Public GroupRefAttr As TxtboxSetting
End Class

这是子对象之一的一部分 classes:

<Serializable>
Public Class TxtboxSetting
    <XmlIgnore()>
    Public MyControl As Windows.Forms.TextBox
    <XmlIgnore()>
    Public DefaultSetting As String

    Private _SavedSetting As String

    Public Property SavedSetting As String
        Get
            Return _SavedSetting
        End Get
        Set(value As String)
            _SavedSetting = value
            CurrentValue = value
        End Set
    End Property

    Public Sub New()
    End Sub

    Public Sub New(DefaultSetting As String, MyControl As Windows.Forms.TextBox)
        Me.DefaultSetting = DefaultSetting
        Me.MyControl = MyControl
    End Sub

    Public Sub RestoreDefault()
        CurrentValue = DefaultSetting
    End Sub
End Class

MySettingsclass的所有子对象,比如GroupRefAttr,都有相同的方法和属性,只是内部代码不同

所以我会有几个 class 像 MySettings class,每个都有不同的子对象。给定这样一个 class 的实例,我想自动遍历字段并在每个字段上调用一个方法 RestoreDefault。我不想知道 MySettings class 中存在哪些对象。相反,知道它们都有 RestoreDefault 方法,我只想在每个对象上调用该方法。

尽管进行了大量搜索,但我还没有找到执行此操作的方法。经过反思,我只能走到这一步:

Dim Opts as New MySettings
For Each var In Opts.GetType.GetFields
    Dim RestoreDefault As System.Reflection.MethodInfo = var.FieldType.GetMethod("RestoreDefault")
    RestoreDefault.Invoke(Opts, Nothing)
Next

但是,在行 RestoreDefault.Invoke(Opts, Nothing) 中,我不能只传入 Opts,因为我正在处理 Opts 中的一个字段,而不是 Opts 本身.像这样的语句是可行的:RestoreDefault.Invoke(Opts.GroupRefAttr, Nothing),但这要求我提前知道 MySettings class 中的对象,这违背了目的。有没有办法在运行时获取字段实例对象并将其关闭?

当您调用 RestoreDefault 方法时,您需要在设置(即字段的值)上调用它,而不是包含该设置的 class。将您的代码更改为此应该可以解决您的问题:

Dim Opts as New MySettings
For Each var In Opts.GetType.GetFields
    Dim setting As Object = var.GetValue(Opts)
    Dim RestoreDefault As System.Reflection.MethodInfo = var.FieldType.GetMethod("RestoreDefault")
    RestoreDefault.Invoke(setting, Nothing)
Next

但是,如果您引入基 class 或接口,您应该能够摆脱部分或全部反射。容器设置 class 可以有一组设置,每个设置都有一个共享基础 class 或与 RestoreDefault 方法的接口。容器设置 class 将通过基础 class 或接口调用此方法,而无需使用反射。

基地class:

Public MustInherit Class BaseSetting
    Public MustOverride Sub RestoreDefault
End Class

具体设置class:

Public Class TxtboxSetting
    Inherits BaseSetting

    Public Overrides Sub RestoreDefault()
        ' Specific implementation
    End Sub
End Class

在从 BaseSetting 派生的任何 class 上,您现在可以调用 RestoreDefault 方法而无需使用反射。

但是,考虑到您的设计,您可能仍希望使用反射来获取 MySettings class 中包含的设置。你可以这样做:

Dim settings = From fieldInfo in Opts.GetType.GetFields
               Where GetType(BaseSetting).IsAssignableFrom(fieldInfo.FieldType)
               Select DirectCast(fieldInfo.GetValue(Opts), BaseSetting)
For Each setting In settings
    setting.RestoreDefault()
Next

反射用于查找从BaseSetting派生的所有字段,然后在每个字段上调用RestoreDefault。此方法不会遇到 "magic string" 问题,即您的代码取决于字符串中表示的 RestoreDefault 方法的名称。

(此外,将 MySettings class 称为 父级 有点误导,因为没有任何内容继承自 MySettings。取而代之的是class 包含其他设置。)

All of the sub-objects of the MySettings class, like GroupRefAttr for example, have the same methods and properties, but the internal code is different.

在这种情况下,应该定义子对象类型,以便它们实现一个通用接口,该接口要求存在这些相同的方法和属性。现在,我将该接口命名为 IControlSetting。然后,您的 For 循环看起来像这样:

Dim Opts as New MySettings
For Each var In Opts.GetType.GetFields
    Dim setting As IControlSetting = TryCast(var.GetValue(Opts), IControlSetting)
    If setting Is Nothing Then Continue
    setting.RestoreDefault()
Next

此外,我会更改您的 MySettings 类型以封装字典或 IControlSetting 对象。然后你可以迭代字典来检查每个对象,而不需要反射。这可能看起来像这样:

Public Class MySettings
    Private allSettings As Dictionary(Of String, IControlSetting)

    Public Sub New()
       allSettings = new Dictionary(Of String, IControlSetting)()

       allSettings.Add("IdentifyByFacType", New RadioButtonSetting())
       allSettings.Add("WtrFacTypes", New ListSetting())
       allSettings.Add("OilFacTypes", New ListSetting())
       '... 
    End Sub

    Public Property IdentifyByFacType As RadioButtonSetting
        Get
            Return DirectCast(allSettings("IdentifyByFacType"), RadioButtonSetting)
        End Get
        'The setters may be optional, depending on how you expect to use these
        Set(ByVal value As RadioButtonSetting)
            allSettings("IdentifyByFacType") = value
        End Set
    End Property

    Public Property WtrFacTypes As ListSetting
        Get
            Return DirectCast(allSettings("WtrFacTypes"), RadioButtonSetting)
        End Get
        Set(ByVal value As ListSetting)
            allSettings("WtrFacTypes") = value
        End Set
    End Property

    Public Property OilFacTypes As ListSetting
        Get
            Return DirectCast(allSettings("OilFacTypes"), RadioButtonSetting)
        End Get
        Set(ByVal value As ListSetting)
            allSettings("OilFacTypes") = value
        End Set
    End Property

    '...


    Public Sub RestoreAllDefaults()
        For Each setting As KeyValuePair(Of String, IControlSetting) In allSettings
            setting.Value.RestoreDefault()
        Next setting
    End Sub     

End Class