Introduction
Extension
methods (Function
or Sub
) are a powerful tool to extend an otherwise sealed object. It's easier than creating aClass
extension because you do not need to update your (maybe already deployed) code, e.g. change Dim nice As SomeClass
to Dim nice As SomeClassExtendedAndNicer
. Obviously, different situations call for different solutions and you need an extended Class
, but I prefer extension methods if I can solve the situation with those. One big huge drawback with extension methods is that you cannot write extension properties! See on MSDN here.
In this tip, I show how to use the Extension
Property in VB.NET Class
Object for a proxy of an extension property or AlmostExtensionProperty
. This tip was inspired by Mr. veen_rp's article: An Almost Extension Property. Instead of by using the tag
property on the Control, and this trick works on the Extension
Property in the VisualBasic
class, so that this trick not only works on the Controls.
VisualBasic Class
First of all, let's see basically how the class object was defined in the VisualBasic
language:
Namespace Microsoft.VisualBasic.Language
Public Class ClassObject
<XmlIgnore> <ScriptIgnore> Public Overridable Property Extension As ExtendedProps
Public Function ReadProperty(Of T)(name As String) As PropertyValue(Of T)
Return PropertyValue(Of T).Read(Me, name)
End Function
End Class
For this part of the class object definition that we can see, every classobject
in VisualBasic may have a property named Extension
, and this property is implemented by using a hash table Dictionary(Of String, Object)
to store the additional tag data so that the dynamics extension property value is going to stored here.
Property Value
By knowing how this extension property works, first we look into how to implement a property. From the language of VB6, the class property is defined by union of a get function and a set method, The property definition in VB.NET goes simple, just using the Property
keyword, then we can simply define a property in a line of code. But by expands the VB.NET class property or viewing the IL code or the reflection result, that we can known that the property it is works as the way of VB6: but the difference is that the property get and set using two inline function in VB.NET. And the Java language also does in this way, using a get
word prefix named function as the property read
method and using a set
word prefix named method as the property write.
So that the extension property in VisualBasic is working as the Class
instance Property
it does: using a get function lambda for get custom value, and using a set method lambda for set the custom value.
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
Public Property obj As ClassObject
Sub New([get] As Func(Of T), [set] As Action(Of T))
__get = [get]
__set = [set]
End Sub
Sub New()
__get = Function() MyBase.Value
__set = Sub(v) MyBase.Value = v
End Sub
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 Extension Property
By invoke get or set the value of the extension property, first we should get the extension property definition, as we should get the PropertyInfo
at first by using the property in the Reflection operation, and here is how to gets the property definition from the Extension
property from the VisualBasic class object.
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
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
Gets the property value definition just very easy, right? We just get the value from the dictionary
by using the property name as the key.
So that this extension property usage may become this style:
- First define an Extension function in a module
- You can use
NameOf
keyword to get property name or just using MethodBase.GetCurrentMethod
for get current function name - Using shared function
PropertyValue(Of <Type>).Read(Of T)
to get the property definition of your ClassObject
And here is a simple example by using this extension property, here is the property definition example:
Public Module PropertyDefinitionModule
<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
<Extension>
Public Function Uid(Of T As ClassObject)(x As T) As PropertyValue(Of Long)
Return PropertyValue(Of Long).Read(Of T)(x, MethodBase.GetCurrentMethod)
End Function
End Module
So that by using this extension property, that for get value, we can do as this:
Dim n As Long = x.Uid
For set property value, that we can write the code:
Dim n As Long = VBMath.Rnd() * 100000000000L
x.Uid.value = n
Call x.Uid.__DEBUG_ECHO()
And here is the code example for 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
{
return PropertyValue<long>.Read<T>(x, MethodBase.GetCurrentMethod());
}
static void Main(string[] args)
{
var x = new ClassObject();
long n = x.myId().Value;
x.myId().Value = 55;
n = -100;
n = x.myId().Value;
n.__DEBUG_ECHO();
Pause();
}
}
}
Extension Property with Linq
Recently, I have lots of work on the bacterial genome annotation, one of this job is using the circos software to visualize the finial result data. For constructing a bacterial genome with some plasmid, its visualization data model in the circos, I using a linq expression to do this job, and the extension property language feature helps me a lot on this job:
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
<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
In some of the situations, we just want to avoid the anonymous type in the code, so that by using the extension property, we can easily extended our class code without modifying the original code, and avoid the anonymous type in the linq.
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)
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