几乎是扩展属性
这是“几乎是扩展属性”的替代方案
引言
扩展 方法(Function 或 Sub)是扩展一个原本密封对象的强大工具。 它比创建 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<<<
        '''
        ''' 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 中获取值。
因此,此扩展属性用法可能变成这种样式
- 首先在模块中定义一个扩展函数
- 您可以使用 NameOf关键字获取属性名称,或者只使用MethodBase.GetCurrentMethod来获取当前函数名称
- 使用共享函数 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




