Vb.net 对对象属性的引用

Vb.net 对对象属性的引用,vb.net,Vb.net,我有一个类型为say Person的对象列表,我想将Person记录导出到excel工作表(我正在使用VB.NET的专有excel组件)。使用带有复选框的表单,用户可以指定应导出哪些人员属性 我没有一个庞大的if-then-else树来检查每个复选框(对应于一个属性)是否已被选中,而是有一个数据结构,其中每个属性都有一个布尔值(选中/未选中)和属性名称作为字符串。然后我使用两个for循环,如下所示: For Each p As Person In Persons ... For

我有一个类型为say Person的对象列表,我想将Person记录导出到excel工作表(我正在使用VB.NET的专有excel组件)。使用带有复选框的表单,用户可以指定应导出哪些人员属性

我没有一个庞大的if-then-else树来检查每个复选框(对应于一个属性)是否已被选中,而是有一个数据结构,其中每个属性都有一个布尔值(选中/未选中)和属性名称作为字符串。然后我使用两个for循环,如下所示:

For Each p As Person In Persons
    ...
    For Each item As ExportColumnData In ExportColumnTable
        ...
        If item.Checked Then
            ...
            Dim o As Object = CallByName(p, item.PropertyName, CallType.Get, Nothing)
            SaveValueToExcelSheet(o)
            ...
        End If
        ...
    Next
    ...
Next
Private Delegate Function GetPersonProperty(x As Person) As Object
chkFullName.Tag = New GetPersonProperty(Function(x As Person) x.FullName)
chkAge.Tag = New GetPersonProperty(Function(x As Person) x.Age)
Dim myDelegate As GetPersonProperty = CType(item.Tag, GetPersonProperty)
Dim value As Object = myDelegate.Invoke(p)

但是,这不是类型安全的,因为我使用CallByName作为字符串提供PropertyName。有没有一种更优雅、更安全的方式可以让我实现同样的目标?我需要某种方法(字符串除外)来引用这些Person对象的属性。

只要
ExportColumnData
中的内容正确,您的解决方案就非常好。如果这些是在运行时动态计算的,您就可以了

否则,或者也可以执行以下操作:使用获取
PropertyInfo
对象的列表。然后,您可以使用这些而不仅仅是字符串来提取循环中的属性值:

Dim o As Object = item.PropertyInfo.GetValue(p, Nothing)

您说您已经有了一个类,您可以在其中存储有关Person类属性的信息。您也可以使用它来存储存储

下面是一个例子:

Class Person
    Public Property Name As String
    Public Property Age As Integer
End Class

Class ExportProperty
    Public Property [Property] As PropertyInfo
    Public Property Export As Boolean
End Class

Sub Main()
    '' Create a List(Of ExportProperty) from all public properties of Person
    Dim properties = GetType(Person).GetProperties() _
                                    .Select(Function(p) New ExportProperty With { .[Property] = p}) _
                                    .ToList()

    '' Say we want to export only the Age field                                     
    properties.Single(Function(p) p. [Property].Name = "Age").Export = True

    '' Create a person instance to export
    Dim pers = New Person With { .Name = "FooBar", .Age = 67 }  

    '' Only export the properties with Export = True
    For Each prop in properties.Where(Function(p) p.Export)

        '' Use the PropertyInfo.GetValue-method to get the value of the property
        '' 
        Console.WriteLine(prop.[Property].GetValue(pers, Nothing))
    Next
End Sub

CallByName
函数使用反射来查找和执行属性getter by string name,因此您可以说,它是不安全的,因为不需要进行编译时检查来确保这些名称的属性确实存在于
Person
类型中

不幸的是,如果没有一个大的
If/Else
块或类似的东西,就没有一种“安全”的方法来进行编译时类型检查。如果希望它在编译时进行检查,则需要在代码中直接按名称调用属性,如果要这样做,则必须在某种类型的大条件块中进行

你可以做一些事情来最小化或改变丑陋的位置。例如,您可以创建所有
Person
属性的枚举,并向
Person
类添加一个方法,该方法使用一个大的
Select Case
块返回给定枚举项的属性值。这将使逻辑可重用,但实际上并没有减少丑陋。不仅如此,这样做会让代码承担类型检查的责任,而不是编译器

或者,例如,您可以将每个
复选框
控件的标记设置为代理,该代理接受
人员
对象,并从给定的
人员
对象返回该选项的正确属性值。然后,在循环中,您可以调用标记中的委托来检索值。例如,如果您有这样一个委托:

For Each p As Person In Persons
    ...
    For Each item As ExportColumnData In ExportColumnTable
        ...
        If item.Checked Then
            ...
            Dim o As Object = CallByName(p, item.PropertyName, CallType.Get, Nothing)
            SaveValueToExcelSheet(o)
            ...
        End If
        ...
    Next
    ...
Next
Private Delegate Function GetPersonProperty(x As Person) As Object
chkFullName.Tag = New GetPersonProperty(Function(x As Person) x.FullName)
chkAge.Tag = New GetPersonProperty(Function(x As Person) x.Age)
Dim myDelegate As GetPersonProperty = CType(item.Tag, GetPersonProperty)
Dim value As Object = myDelegate.Invoke(p)
然后您可以设置
复选框的
标记,如下所示:

For Each p As Person In Persons
    ...
    For Each item As ExportColumnData In ExportColumnTable
        ...
        If item.Checked Then
            ...
            Dim o As Object = CallByName(p, item.PropertyName, CallType.Get, Nothing)
            SaveValueToExcelSheet(o)
            ...
        End If
        ...
    Next
    ...
Next
Private Delegate Function GetPersonProperty(x As Person) As Object
chkFullName.Tag = New GetPersonProperty(Function(x As Person) x.FullName)
chkAge.Tag = New GetPersonProperty(Function(x As Person) x.Age)
Dim myDelegate As GetPersonProperty = CType(item.Tag, GetPersonProperty)
Dim value As Object = myDelegate.Invoke(p)
然后,在循环中,可以调用
标记中的委托来获取值,如下所示:

For Each p As Person In Persons
    ...
    For Each item As ExportColumnData In ExportColumnTable
        ...
        If item.Checked Then
            ...
            Dim o As Object = CallByName(p, item.PropertyName, CallType.Get, Nothing)
            SaveValueToExcelSheet(o)
            ...
        End If
        ...
    Next
    ...
Next
Private Delegate Function GetPersonProperty(x As Person) As Object
chkFullName.Tag = New GetPersonProperty(Function(x As Person) x.FullName)
chkAge.Tag = New GetPersonProperty(Function(x As Person) x.Age)
Dim myDelegate As GetPersonProperty = CType(item.Tag, GetPersonProperty)
Dim value As Object = myDelegate.Invoke(p)
但对于这么简单的任务来说,这太复杂了


最后,如果编译时类型检查真的很重要,我会咬紧牙关,创建一个大的条件块。如果没有那么重要,我就坚持使用反射,并在代码中添加一些适当的异常处理。

虽然这是使用反射的另一种方式的有趣示例,但我看不出这是如何解决类型检查问题的。您仍在使用反射按字符串名称搜索属性。@Stevendogart这是一种更优雅的访问属性的方法,只需稍加努力,就可以比使用
Delegate.CreateDelegate
(如果确实需要)调用ByName
快得多。此外,由于属性不是由字符串访问的,因此可以确保每个属性实际上都存在于
Person
类型中。我曾考虑过使用
GetProperties()
,但我需要高度控制向用户提供哪些属性以供导出。在我看来,使用
GetProperties()
有点困难。我的问题是ExportColumnData中的内容在运行时不是动态计算的——它只是一个硬编码字符串。正如我所说,您可以在运行时使用
Type.GetProperties
来计算它们(Dominic Kexel的回答给出了实现这一点的实际详细代码)。如果由于某种原因无法实现,您必须按照Steven Doggart的回答进行。编译时检查没有那么重要,但由于我一直在开发导出功能,这种丑陋一直困扰着我。因此我认为一定有更好的方法。您提到的委托如何工作?我可以为对象本身指定委托吗领带?这正是我需要的!