Results 1 to 9 of 9

Thread: VB.NET Extension Methods primary

  1. #1

    Thread Starter
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,713

    VB.NET Extension Methods primary

    This articles intent is to provide some basics to writing extension methods with practical examples along with references at the end of the article. Please note that the “how-to” is emphasized here over actual code within the extensions since if you were to ask 10 programmers how to do task A there will more likely than not at least five different ways to accomplish the task.

    One of the features I embraced when VS2008 arrived was extension method (I tend to call them language extensions) as my style of programming is declarative in nature so this was a natural progression for me.

    When reading various documents from MSDN web site along with the sparse amount of information I did pick it up easily. Also realized that for some developers not in the game for very long may not see the benefits of using extensions along with others who might abuse extension or would shun them if the few limitations caused them discomfort.

    From MSDN Visual Basic 2008 introduces extension methods, which enable developers to add custom functionality to data types that are already defined without creating a new derived type. Extension methods make it possible to write a method that can be called as if it were an instance method of the existing type.
    http://msdn.microsoft.com/en-us/libr...36(VS.90).aspx

    Okay my intent is to show the average developer useful ways to use extension methods in their every day task of developing solutions. If you want in-depth information, check out the links at the bottom of this article.

    First steps: Do not write an extension simply because you can but instead because it serves a valid purpose over writing a convention function or procedure. Secondly, write the extension as a conventional function or sub until you get the hang of writing extension methods. Another consideration is how show exceptions be handled and finally in regards to starting out writing extensions think about their scope.
    Last edited by kareninstructor; Aug 11th, 2011 at 08:04 AM.

  2. #2

    Thread Starter
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,713

    Re: VB.NET Extension Methods primary

    A common operation is to check to see if a String has a value.
    Code:
    Dim SomeValue As String = ""
    If String.IsNullOrEmpty(SomeValue) Then
        Console.WriteLine("Empty")
    Else
        Console.WriteLine("Not empty")
    End If
    In the above example, the string is empty and the code worked as expected but is not very declarative plus this code logic if used a lot is not so easy to upgrade if a better method comes along. In this case under VS2010 we have been given IsNullOrWhiteSpace Method which means if you want to upgrade your code you will need to change every instance of one method to another method in your projects.

    A better way is to create a centralized function so that change happens in one place rather in many places. Back to extension methods, for this example of checking if a string is empty what would be a good name for such an extension? Well the best thought process is to find another type which has a similar method name such as Nullable Integer which has .HasValue. So we create

    Code:
    Public Function HasValue(ByVal Value As String) As Boolean
        Return Not String.IsNullOrEmpty(Value)
    End Function
    And test it to make sure it works as expected.

    Code:
    Dim SomeValue As String = Nothing
    If HasValue(SomeValue) Then
        Console.WriteLine("Not empty")
    Else
        Console.WriteLine("Empty")
    End If
    Next convert the function to an extension method by adding a Extension Attribute

    Code:
    <System.Runtime.CompilerServices.Extension()>
    So now the function turned extension method looks like this.

    Code:
    <System.Runtime.CompilerServices.Extension()> _
    Public Function HasValue(ByVal Value As String) As Boolean
        Return Not String.IsNullOrEmpty(Value)
    End Function
    And to call the extension method
    Code:
    If SomeValue.HasValue Then
        Console.WriteLine("Not empty")
    Else
        Console.WriteLine("Empty")
    End If
    And we can still call it as a regular function
    Code:
    If HasValue(SomeValue) Then
        Console.WriteLine("Not empty")
    Else
        Console.WriteLine("Empty")
    End If
    There are some issues as in

    Code:
    If HasValue(SomeValue) Then
        Console.WriteLine("Not empty")
    Else
        Console.WriteLine("Empty")
    End If
    <System.Runtime.CompilerServices.Extension()> _
    Public Function HasValue(ByVal sender As String) As Boolean
        Return Not String.IsNullOrEmpty(sender)
    End Function
    <Runtime.CompilerServices.Extension()> _
    Public Function HasValue(ByVal sender() As String) As Boolean
        Dim Result As Boolean = False
        If sender IsNot Nothing Then
            Result = sender.Count > 0
        End If
        Return Result
    End Function

  3. #3

    Thread Starter
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,713

    Re: VB.NET Extension Methods primary

    Essentially, we have two functions that accept different types, which produce an ambiguous IDE error. The way around this if you want to call the function as a regular function instead of a extension method is to prefix the name space if different from the project name space or simple prefix the function call with the module name where the proper function to use is located in.

    For this example the function is in StringMethods.vb

    Code:
    If StringMethods.HasValue(SomeValue) Then
        Console.WriteLine("Not empty")
    Else
        Console.WriteLine("Empty")
    End If
    While discussing code modules, extensions must reside in code modules and not forms. When naming these modules I suggest giving them meaningful names like StringMethods.vb for string extensions and for arrays ArrayMethods.vb and so on.

    Code:
    Dim SomeValue As String = Nothing
    If SomeValue.HasValue() Then
        Console.WriteLine("SomeValue is Not empty")
    Else
        Console.WriteLine("SomeValue is Empty")
    End If
    SomeValue = "Got some text"
    If SomeValue.HasValue Then
        Console.WriteLine("SomeValue is Not empty")
    Else
        Console.WriteLine("SomeValue is Empty")
    End If
    
    Dim SomeNullableInteger As New Nullable(Of Integer)
    If SomeNullableInteger.HasValue Then
        Console.WriteLine("SomeNullableIneger has a value")
    Else
        Console.WriteLine("SomeNullableIneger does not has a value")
    End If
    SomeNullableInteger = 100
    If SomeNullableInteger.HasValue Then
        Console.WriteLine("SomeNullableIneger has a value")
    Else
        Console.WriteLine("SomeNullableIneger does not has a value")
    End If
    So now when checking to see if a string or Nullable type (an Integer in this example) is empty the same method name is used.
    For a medium challenge, let’s take an array of strings where the goal is to get only integer elements. The following code would do the trick.


    Code:
    Dim Values() As String = {"1", "2", "-3", "x2", "300", "", "Hello"}
    
    Dim Numbers_A = (From x In Values _
                     Where IsNumeric(x) _
                     Select CInt(x)).ToArray
    If Numbers_A.Count > 0 Then
        For Each Number In Numbers_A
            Console.WriteLine("{0,15}", Number)
        Next
    End If
    Well we can do a little better than using IsNumeric by using Integer.TypeParse

    Code:
    Dim Numbers_B = (From x In Values _
                     Where Integer.TryParse(x, Nothing) _
                     Select Convert.ToInt32(x)).ToArray
    Or how about the following which is an extension method of type Integer.

    Code:
    Dim Numbers_C = (From x In Values Where x.IsNumeric Select CInt(x)).ToArray
    <Runtime.CompilerServices.Extension()> _
    Public Function IsNumeric(ByVal sender As String) As Boolean
        Return Integer.TryParse(sender, Nothing)
    End Function
    Or better yet

    Code:
    <Runtime.CompilerServices.Extension()> _
    Public Function IsInteger(ByVal sender As String) As Boolean
        Return Integer.TryParse(sender, Nothing)
    End Function
    <Runtime.CompilerServices.Extension()> _
    Public Function IsDouble(ByVal sender As String) As Boolean
        Return Double.TryParse(sender, Nothing)
    End Function
    Please note that I am not saying you should replace IsNumeric but simply using a this for a simple example.

    Another thought when developing extension methods is coupling them together, for instance the following extension method checks to see if a Integer value is between a low and high integer value.

    Code:
    <System.Runtime.CompilerServices.Extension()> _
    Public Function IsBetween( _
        ByRef sender As Integer, _
        ByRef LowerLimit As Integer, _
        ByRef UpperLimit As Integer) As Boolean
    
        If sender < LowerLimit Or sender > UpperLimit Then Return False
        Return True
    End Function

  4. #4

    Thread Starter
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,713

    Re: VB.NET Extension Methods primary

    Implemented

    Code:
    Dim IntegerTestBetween As Integer = 100
    Console.WriteLine("IntegerTestBetween {0}", IntegerTestBetween)
    If IntegerTestBetween.IsBetween(101, 105) Then
        Console.WriteLine("Value is between 101 and 105")
    Else
        Console.WriteLine("Value is not between 101 and 105")
    End If
    If IntegerTestBetween.IsBetween(50, 101) Then
        Console.WriteLine("Value is between 50 and 101")
    Else
        Console.WriteLine("Value is not between 50 and 101")
    End If
    The IsBetween extension can be used to check if a character is between two characters.

    Code:
    <System.Runtime.CompilerServices.Extension()> _
    Public Function IsBetween(ByVal sender As Char, _
                              ByVal LowerLimit As Char, ByVal UpperLimit As Char) As Boolean
        Dim Value As Integer = Asc(sender)
        Return Value.IsBetween(Asc(LowerLimit), Asc(UpperLimit))
    End Function
    In the IsBetween extension method for characters, the return line uses the IsBetween extension method for Integers.

    Using extension methods with data routines is great too, like when you retrieve data from a database into a DataTable, which becomes the source of a DataGridView. Once the data is loaded the user wants to save the current view to an XML file so you might code this.

    Code:
    DirectCast(bsData.DataSource, DataTable).WriteXml("SampleData.xml")
    If IO.File.Exists("SampleData.xml") Then
        MsgBox("Data successfully exported")
    Else
        MsgBox("Failed to export data")
    End If
    We know casting the BindingSource DataSource is safe because we set the BindingSource DataSource from a DataTable. For me I would rather be declarative and not see the casting so we ended up with another extension method for casting the DataSource of any BindingSource called .DataTable.

    Code:
    bsData.DataTable.WriteXml("SampleData.xml")
    If IO.File.Exists("SampleData.xml") Then
        MsgBox("Data successfully exported")
    Else
        MsgBox("Failed to export data")
    End If
    <System.Runtime.CompilerServices.Extension()> _
    Public Function DataTable(ByVal sender As BindingSource) As DataTable
        Return DirectCast(sender.DataSource, DataTable)
    End Function
    If for some reason you did not load the data or did not have your morning coffee and the BindingSource DataSource is not a DataTable a run time exception will be thrown. We could handle it as follows;

    Code:
    <System.Diagnostics.DebuggerStepThrough()> _
    <System.Runtime.CompilerServices.Extension()> _
    Public Function DataSourceIsDataTable(ByVal sender As BindingSource) As Boolean
        Return sender.DataSource.GetType.Equals(GetType(System.Data.DataTable))
    End Function
    If bsData.DataSourceIsDataTable Then
        bsData.DataTable.WriteXml("SampleData.xml")
        If IO.File.Exists("SampleData.xml") Then
            MsgBox("Data successfully exported")
        Else
            MsgBox("Failed to export data")
        End If
    Else
        ' . . .
    End If
    If you test well then how about doing your check within the DataTable extension?

    Code:
    <System.Runtime.CompilerServices.Extension()> _
    Public Function DataTable(ByVal sender As BindingSource) As DataTable
        If sender.DataSourceIsDataTable Then
            Return DirectCast(sender.DataSource, DataTable)
        Else
            Throw New Exception("DataSource is not a DataTable")
        End If
    End Function
    An important thing to know is that using a standard code module the header is as follows

    Code:
    Module DateMethods
    Where all the extensions within are public to code in the current project but not to other support projects. Example, you create a DLL with all your extension methods so they can be used in several projects. As the module sits no other projects within a solution will see these extensions unless you prefix Module with Public.

    Code:
    Public Module DateMethods
    The mark of a professional will add a header to each extension so that when someone consumes your extension method they have information through intellisense. Of course if you give a good name to the extension method the information is not so important.

    Code:
    ''' <summary>
    ''' Indicates if the current row is a new row
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <System.Diagnostics.DebuggerStepThrough()> _
    <System.Runtime.CompilerServices.Extension()> _
    Public Function IsNewRow(ByVal sender As BindingSource) As Boolean
       Return DirectCast(sender.Current, DataRowView).Row.RowState = DataRowState.Detached
    End Function
    So with that said the following information would be helpful so when the user gets an different size array back they know this ahead of time.

    Code:
    ''' <summary>
    ''' Creates a new array of integer values and discards all other types
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' If the original array (sender) has 10 elements where seven are
    ''' valid integers the resulting array will have seven elements
    ''' </remarks>
    <Runtime.CompilerServices.Extension()> _
    Public Function ToIntArray(ByVal sender() As String) As Integer()
        Return (From x In sender Where Integer.TryParse(x, Nothing) Select CInt(x)).ToArray
    End Function
    To be continued

  5. #5

    Thread Starter
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,713

    Re: VB.NET Extension Methods primary


  6. #6
    Frenzied Member
    Join Date
    Jul 2006
    Location
    MI
    Posts
    2,012

    Re: VB.NET Extension Methods primary

    A very nice write up. I will have to give these a try sometime. Thanks...

  7. #7

    Thread Starter
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,713

    Database connection extension methods suggestions

    Let’s say you have a database connection object that is opened/closed while the form or class is being used you would use logic similar to the following

    Code:
    If MyFormAppConnection.State = ConnectionState.Closed Then
       MyFormAppConnection.Open()
    End If
    Using an extension method we can place the logic into the method and give it a easy to remember name such as the following

    Code:
    If MyFormAppConnection.IsClosed Then
       MyFormAppConnection.Open()
    End If
    Another name for the method (not my cup of tea but simply show this is possible)
    Code:
    If MyFormAppConnection.NeedsToBeOpened Then
       MyFormAppConnection.Open()
    End If
    The key is to give the method a name that indicates what it is doing for you without having to look into the method.

    Now suppose when the connection was closed the code set the connection to nothing we need to check prior to opening the connection as follows.

    Code:
    If MyFormAppConnection Is Nothing Then
       MyFormAppConnection = New OleDb.OleDbConnection(ConnectionString)
    End If
    Using a extension method
    Code:
    If MyFormAppConnection.IsNothing Then
       MyFormAppConnection = New OleDb.OleDbConnection(ConnectionString)
    End If
    Where the beauty is with the examples above is when using multiple data providers in a project we can have the same extension methods for both providers. In the example below the IsNothing method is for both OleDb and IBM DB2.

    Code:
    <System.Diagnostics.DebuggerStepThrough()> _
    <Runtime.CompilerServices.Extension()> _
    Public Function IsNothing(ByVal sender As OleDb.OleDbConnection) As Boolean
       Return sender Is Nothing
    End Function
    <System.Diagnostics.DebuggerStepThrough()> _
    <Runtime.CompilerServices.Extension()> _
    Public Function IsNothing(ByVal sender As DB2.iSeries.iDB2Connection) As Boolean
       Return sender Is Nothing
    End Function
    The attached demonstration project was done in VS2008 using LINQ and embedded expressions in the connection string which was done because this is easier to read then how programmers generally do connection string as in concatenation of the string. Personally my connection strings are in an XML settings file so I never have to see them in my code.

    Code:
    Private ConnectionString As String = _
          <Text>
             Provider=Microsoft.Jet.OLEDB.4.0;
             Data Source=<%= Application.StartupPath %>\;
             Extended Properties='text;HDR=No'
          </Text>.Value
    How most developers do connection strings (when done cleanly)
    Code:
    Private ConnectionString As String = String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};", MyFileName)
    Last edited by kareninstructor; Aug 11th, 2011 at 08:04 AM.

  8. #8
    Stack Overflow mod​erator
    Join Date
    May 2008
    Location
    British Columbia, Canada
    Posts
    2,824

    Re: VB.NET Extension Methods primary

    And remember to pass variables ByVal unless they need to be ByRef to avoid confusion, use OrElse instead of Or, and
    Code:
    <Runtime.CompilerServices.Extension()> _
    Public Function HasValue(ByVal sender() As String) As Boolean
        Dim Result As Boolean = False
        If sender IsNot Nothing Then
            Result = sender.Count > 0
        End If
        Return Result
    End Function
    could be
    Code:
    <Runtime.CompilerServices.Extension()> _
    Public Function HasValue(ByVal sender() As String) As Boolean
        Return sender IsNot Nothing AndAlso sender.Count > 0
    End Function
    Because of the new (relative to VB 2003) AndAlso and OrElse operators.

  9. #9

    Thread Starter
    Karen Payne MVP kareninstructor's Avatar
    Join Date
    Jun 2008
    Location
    Oregon
    Posts
    6,713

    Extension for apostrophe in SQL statements

    One of my most used extension method s underlying logic is to circumvent an error from a data provider when an apostrophe is entered into a TextBox which is used in a WHERE statement of an SQL statement. The apostrophe bug where best seen in the SQL statement example below (figure 1) where the apostrophe after the “I” in “I’m” will be treat as the end of the SQL statement string and when the database engine runs into the last apostrophe an error is generated so we need to add logic to replace single apostrophes with double apostrophes (figure 2). Of course if you use parameterized statements you may find out that this problem is null and void but my guess is that many developers will forgo using parameterized SQL.

    Note 1 that in this case an apostrophe is also known as an escape character.
    Note 2 some databases do not have this problem when your SQL statement is put together using parameters.

    Figure 1
    Code:
    INSERT INTO Customers (F1,F2,F3...) VALUES ('Hi folks, I'm a developer', ‘’, ‘’ . . .)
    Figure 2
    Code:
    Dim QueryStatement As String = _
        "SELECT * FROM Customers " & _
        "WHERE CompanyName ='" & _
        txtSearchForValue.Text.Replace("'", "''") & "'"
    When writing and debugging complex SQL statements it might be helpful to have methods to show where there are issues like when creating an insert statement where the underlying table has constraints against empty fields. We could create extensions, which allow you to see them easily. Figure 3 is one example which when used as in figure 4 if a value is empty it will show up as shown in figure 5 where the first parameter value is empty because the user only entered a contact and not a company name. Of course there should be logic to prevent this from ever happening, instead this is an example for checking values in an insert statement, use your imagination.

    Figure 3
    Code:
    Private Const NULL_INDICATOR As String = "NULL"
    <System.Diagnostics.DebuggerStepThrough()> _
    <Runtime.CompilerServices.Extension()> _
    Public Function sqlTextNullDetection(ByVal sender As String) As String
        Dim Result As String = sender
        If Not sender.HasValue Then
            Result = NULL_INDICATOR
        End If
    
        Return Result.SqlEncode
    
    End Function
    Figure 4
    Code:
    Dim Sample As String = _
    <SQL>
    INSERT INTO Company
    (
        Name,
        Contact
    )
    VALUES
    (
        '<%= txtCompanyName.Text.sqlTextNullDetection %>',
        '<%= txtCompanyContact.Text.sqlTextNullDetection %>'
    )           
    </SQL>.Value
    txtInsertQueryDebugging.Text = Sample.TrimEmbeddedQueryText
    Figure 5
    Code:
    INSERT INTO Company (Name, Street ) VALUES ( 'NULL', 'John Doe' )
    You might be thinking that in figure 4 we need to have an oversized text box since the example SQL statement spans multiple lines. This is handled by an extension method TrimEmbeddedQueryText which in short, flattens the SQL statement to one line shown in figure 6.

    Figure 6
    Code:
    Imports System.Text.RegularExpressions
    Module FixJaggedSQL_Method
        <System.Diagnostics.DebuggerStepThrough()> _
        <Runtime.CompilerServices.Extension()> _
        Function TrimEmbeddedQueryText(ByVal value As String) As String
            Return Regex.Replace(value.TrimStart, " {2,}", " ").Replace(Chr(10), "")
        End Function
    End Module
    Notable
    Using Parameters in ADO.NET http://jmcilhinney.blogspot.com/2009...in-adonet.html
    SQL Server (2008 R2) QUOTENAME (Transact-SQL)
    http://msdn.microsoft.com/en-us/library/ms176114.aspx
    SQL-Server
    SET QUOTED_IDENTIFIER = [On/Off]
    http://msdn.microsoft.com/en-us/libr...v=sql.80).aspx
    ADO.NET Configuring parameters
    http://msdn.microsoft.com/en-us/library/yy6y35y8.aspx
    DbParameter Class http://msdn.microsoft.com/en-us/libr...parameter.aspx
    Last edited by kareninstructor; Aug 11th, 2011 at 08:04 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width