几乎是扩展属性
这是“几乎是扩展属性”的替代方案
引言
扩展
方法(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