65.9K
CodeProject 正在变化。 阅读更多。
Home

几乎是扩展属性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.22/5 (4投票s)

2016年6月24日

CPOL

3分钟阅读

viewsIcon

16221

downloadIcon

120

这是“几乎是扩展属性”的替代方案

引言

扩展 方法(FunctionSub)是扩展一个原本密封对象的强大工具。 它比创建 Class 扩展更容易,因为您不需要更新您的(可能已经部署的)代码,例如将 Dim nice As SomeClass 更改为 Dim nice As SomeClassExtendedAndNicer。 显然,不同的情况需要不同的解决方案,您需要一个扩展的 Class,但如果我能用这些方法解决问题,我更喜欢扩展方法。 扩展方法的一个巨大的缺点是您无法编写扩展属性! 请参阅 此处 MSDN

在本技巧中,我展示了如何在 VB.NET Class 对象中使用 Extension 属性作为扩展属性或 AlmostExtensionProperty 的代理。 本技巧的灵感来自于 veen_rp 先生的文章:一个几乎的扩展属性。 它没有使用 Control 上的 tag 属性,并且这个技巧在 VisualBasic 类的 Extension 属性上起作用,因此这个技巧不仅适用于控件。

VisualBasic 类

首先,让我们看看基本上如何在 VisualBasic 语言中定义类对象

Namespace Microsoft.VisualBasic.Language

    ''' <summary>
    ''' The base class object in VisualBasic language
    ''' </summary>
    Public Class ClassObject

        ''' <summary>
        ''' The extension property.(为了节省内存的需要,这个附加属性尽量不要被自动初始化)
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks>
        ''' Dummy field for solve the problem of xml serialization >>>simpleContent&lt;&lt;&lt;
        '''
        ''' http://stackoverflow.com/questions/2501466/xmltext-attribute-in-base-class-breakes-serialization
        '''
        ''' So I think you could make it work by adding a dummy property or field 
        ''' that you never use in the LookupItem class.
        ''' If you're never assign a value to it, it will remain null and will not be serialized, 
        ''' but it will prevent your class from being treated as simpleContent. 
        ''' I know it's a dirty workaround, but I see no other easy way...
        ''' </remarks>
        <XmlIgnore> <ScriptIgnore> Public Overridable Property Extension As ExtendedProps

        ''' <summary>
        ''' Get dynamics property value.
        ''' </summary>
        ''' <typeparam name="T"></typeparam>
        ''' <param name="name"></param>
        ''' <returns></returns>
        Public Function ReadProperty(Of T)(name As String) As PropertyValue(Of T)
            Return PropertyValue(Of T).Read(Me, name)
        End Function

        ' ......
    End Class

对于我们可以看到的类对象定义部分,VisualBasic 中的每个 classobject 都可能有一个名为 Extension 的属性,并且该属性是通过使用哈希表 Dictionary(Of String, Object) 来实现的,用于存储附加的标签数据,以便将动态扩展属性值存储在此处。

属性值

通过了解此扩展属性的工作方式,首先我们研究如何实现一个属性。 从 VB6 语言来看,类属性是通过 get 函数和 set 方法的联合定义的,VB.NET 中的属性定义很简单,只需使用 Property 关键字,然后我们可以简单地在一行代码中定义一个属性。 但是通过扩展 VB.NET 类属性或查看 IL 代码或反射结果,我们可以知道该属性的工作方式与 VB6 相同:但不同之处在于该属性使用 VB.NET 中的两个内联函数进行 get 和 set。 Java 语言也以这种方式进行,使用一个以 get 单词为前缀的函数作为属性 read 方法,并使用一个以 set 单词为前缀的方法作为属性 write 方法。

因此,VisualBasic 中的扩展属性就像 Class 实例 Property 一样工作:使用一个 get 函数 lambda 来获取自定义值,并使用一个 set 方法 lambda 来设置自定义值。

Public Class PropertyValue(Of T) : Inherits Value(Of T)

    ReadOnly __get As Func(Of T)
    ReadOnly __set As Action(Of T)

    Public Overrides Property Value As T
        Get
            Return __get()
        End Get
        Set(value As T)
            MyBase.Value = value
            If Not __set Is Nothing Then
                Call __set(value)  
            End If
        End Set
    End Property

    ''' <summary>
    ''' The instance object for this extension property
    ''' </summary>
    ''' <returns></returns>
    Public Property obj As ClassObject

    ''' <summary>
    '''
    ''' </summary>
    ''' <param name="[get]">请勿使用<see cref="GetValue"/></param>函数,否则会出现栈空间溢出
    ''' <param name="[set]">请勿使用<see cref="SetValue"/></param>方法,否则会出现栈空间溢出
    Sub New([get] As Func(Of T), [set] As Action(Of T))
        __get = [get]
        __set = [set]
    End Sub

    ''' <summary>
    ''' 默认是将数据写入到基本类型的值之中
    ''' </summary>
    Sub New()
        __get = Function() MyBase.Value
        __set = Sub(v) MyBase.Value = v
    End Sub

    ''' <summary>
    ''' 这个主要是应用于Linq表达式之中,将属性值设置之后返回宿主对象实例
    ''' </summary>
    ''' <param name="value"></param>
    ''' <returns></returns>
    Public Function SetValue(value As T) As ClassObject
        Call __set(value)
        Return obj
    End Function

    Public Overloads Shared Narrowing Operator CType(x As PropertyValue(Of T)) As T
        Return x.Value
    End Operator

    Public Overrides Function ToString() As String
        Return Value.GetJson
    End Function

    ' ......
End Class

获取扩展属性

通过调用 get 或 set 扩展属性的值,首先我们应该获取扩展属性定义,因为我们应该首先使用反射操作中的属性获取 PropertyInfo,这里是如何从 VisualBasic 类对象的 Extension 属性中获取属性定义。

Public Shared Function [New](Of Cls As ClassObject)(x As Cls, name As String) As PropertyValue(Of T)
    Dim value As New PropertyValue(Of T)()
    x.Extension.DynamicHash.Value(name) = value
    value.obj = x
    Return value
End Function

''' <summary>
''' 读取<see cref="ClassObject"/>对象之中的一个拓展属性
''' </summary>
''' <typeparam name="Cls"></typeparam>
''' <param name="x"></param>
''' <param name="name"></param>
''' <returns></returns>
Public Shared Function Read(Of Cls As ClassObject)(x As Cls, name As String) As PropertyValue(Of T)
    If x.Extension Is Nothing Then
        x.Extension = New ExtendedProps
    End If
    Dim prop As Object = x.Extension.DynamicHash(name)
    If prop Is Nothing Then
        prop = PropertyValue(Of T).[New](Of Cls)(x, name)
    End If
    Return DirectCast(prop, PropertyValue(Of T))
End Function

获取属性值定义非常容易,对吧? 我们只需使用属性名称作为键从 dictionary 中获取值。

因此,此扩展属性用法可能变成这种样式

  1. 首先在模块中定义一个扩展函数
  2. 您可以使用 NameOf 关键字获取属性名称,或者只使用 MethodBase.GetCurrentMethod 来获取当前函数名称
  3. 使用共享函数 PropertyValue(Of <Type>).Read(Of T) 来获取您的 ClassObject 的属性定义

这里是一个使用此扩展属性的简单示例,这里是属性定义示例

Public Module PropertyDefinitionModule

    ''' <summary>
    ''' Example of the extension property in VisualBasic
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="x"></param>
    ''' <returns></returns>
    <Extension>
    Public Function Uid(Of T As ClassObject)(x As T) As PropertyValue(Of Long)
        Return PropertyValue(Of Long).Read(Of T)(x, NameOf(Uid))
    End Function

    ' Or

    ''' <summary>
    ''' Example of the extension property in VisualBasic
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="x"></param>
    ''' <returns></returns>
    <Extension>
    Public Function Uid(Of T As ClassObject)(x As T) As PropertyValue(Of Long)
        ' Just copy this statement without any big modification.
        ' just modify the generics type constraint to use this extension property language feature.
        Return PropertyValue(Of Long).Read(Of T)(x, MethodBase.GetCurrentMethod)
    End Function
End Module

因此,通过使用此扩展属性,对于获取值,我们可以这样做

Dim n As Long = x.Uid

对于设置属性值,我们可以编写代码

Dim n As Long = VBMath.Rnd() * 100000000000L
x.Uid.value = n
Call x.Uid.__DEBUG_ECHO()

这里是 C# 的代码示例

using Microsoft.VisualBasic;
using Microsoft.VisualBasic.ComponentModel.DataSourceModel;
using Microsoft.VisualBasic.Language;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using static Microsoft.VisualBasic.Extensions;

namespace Test2
{
    public static class Program
    {

        public static PropertyValue<long> myId<T>(this T x) where T : ClassObject
        {
            // Just copy this statement without any big modification. 
            // just modify the generics type constraint.
            return PropertyValue<long>.Read<T>(x, MethodBase.GetCurrentMethod());
        }

        static void Main(string[] args)
        {
            var x = new ClassObject();
            long n = x.myId().Value; // The init value is ZERO

            x.myId().Value = 55;     // Extension property set value

            n = -100;
            n = x.myId().Value;      // Extension property get value, value should be 55 not -100
            n.__DEBUG_ECHO();        // display the value

            Pause();
        }
    }
}

带有 Linq 的扩展属性

最近,我做了很多关于细菌基因组注释的工作,其中一项工作是使用 circos 软件来可视化最终的结果数据。 为了构建一个带有某些质粒的细菌基因组,它在 circos 中的可视化数据模型,我使用了一个 linq 表达式来完成这项工作,并且扩展属性语言特性在此工作中帮助了我很多

Imports Microsoft.VisualBasic.Language

Public Class Karyotype : Inherits ClassObject
    Implements IKaryotype

    Public Property chrName As String Implements IKaryotype.chrName
    Public Property chrLabel As String
    Public Property start As Integer Implements IKaryotype.start
    Public Property [end] As Integer Implements IKaryotype.end
    Public Property color As String Implements IKaryotype.color

    Public Overrides Function ToString() As String Implements IKaryotype.GetData
        Return $"chr - {chrName} {chrLabel} {start} {[end]} {color}"
    End Function
End Class
Public Module KaryotypeExtensions

    ''' <summary>
    ''' nt核苷酸基因组序列拓展属性
    ''' </summary>
    ''' <param name="x"></param>
    ''' <returns></returns>
    <Extension>
    Public Function nt(x As Karyotype) As PropertyValue(Of FastaToken)
        Return PropertyValue(Of FastaToken).Read(Of Karyotype)(x, NameOf(nt))
    End Function
End Module

在某些情况下,我们只想避免代码中的匿名类型,因此,通过使用扩展属性,我们可以轻松地扩展我们的类代码,而无需修改原始代码,并避免 linq 中的匿名类型。

''' <summary>
''' Creates the model for the multiple chromosomes genome data in circos.(使用这个函数进行创建多条染色体的)
''' </summary>
''' <param name="source">Band数据</param>
''' <param name="chrs">karyotype数据</param>
''' <returns></returns>
Public Shared Function FromBlastnMappings(
                       source As IEnumerable(Of BlastnMapping), 
                       chrs As IEnumerable(Of FastaToken)) As BasicGenomeSkeleton

    Dim ks As Karyotype() =
        LinqAPI.Exec(Of Karyotype) <= From nt As SeqValue(Of FastaToken)
                                      In chrs.SeqIterator
                                      Let name As String = _
                                          nt.obj.Title.NormalizePathString(True).Replace(" ", "_")
                                      Select New Karyotype With {
                                          .chrName = "chr" & nt.i,
                                          .chrLabel = name,
                                          .color = "",
                                          .start = 0,
                                          .end = nt.obj.Length
                                      }.nt.SetValue(nt.obj).As(Of Karyotype) ' using extension property 
                                                                             ' for avoid anonymous type

    ' using the data from the extension property
    Dim labels As Dictionary(Of String, Karyotype) = 
        ks.ToDictionary(Function(x) x.nt.Value.Title, Function(x) x)

    Dim bands As List(Of Band) =
        LinqAPI.MakeList(Of Band) <= From x As SeqValue(Of BlastnMapping)
                                     In source.SeqIterator
                                     Let chr As String = labels(x.obj.Reference).chrName
                                     Let loci As NucleotideLocation = x.obj.MappingLocation
                                     Select New Band With {
                                         .chrName = chr,
                                         .start = loci.Left,
                                         .end = loci.Right,
                                         .color = "",
                                         .bandX = "band" & x.i,
                                         .bandY = "band" & x.i
                                    }
    Return New BasicGenomeSkeleton With {
         .__bands = bands,
         .__karyotypes = New List(Of Karyotype)(ks)
    }
End Function
© . All rights reserved.