List transactions

Posted Fri, Dec 19 2008 0:55 by bill

From a recent discussion on using lists, and whether or not you can remove items in while iterating, I decided to write an extension that allows you to get a “transaction” for an IList(Of t).  This ListTransaction(Of T) caches adds and removes until you call commit (kudos to Duncan for the idea !!).  It also implements IDisposable so you can use a using scope, eg:

 

      Using listTran = mylist.GetTransaction

         For Each item In mylist

            ' other code here

            listTran.Remove(item)

         Next

      End Using

 

And here’s the implementation:

 

Module ListExtensions

 

   <Runtime.CompilerServices.Extension()> _

   Function GetTransaction(Of T)(ByVal list As IList(Of T)) As ListTransaction(Of T)

      Return New ListTransaction(Of T)(list)

   End Function

 

 

   Public Class ListTransaction(Of T)

      Implements IDisposable

 

      Private _list As IList(Of T)

      Private _adds As List(Of T)

      Private _removes As List(Of T)

 

      Public Sub New(ByVal list As IList(Of T))

         _list = list

      End Sub

 

      Public Sub Add(ByVal item As T)

         If _adds Is Nothing Then _adds = New List(Of T)

         _adds.Add(item)

      End Sub

 

      Public Sub Remove(ByVal item As T)

         If _removes Is Nothing Then _removes = New List(Of T)

         _removes.Add(item)

      End Sub

 

      Public Sub Commit()

         If _list Is Nothing Then Exit Sub

         If _adds IsNot Nothing Then

            For Each item As T In _adds

               _list.Add(item)

            Next

            _adds = Nothing

         End If

         If _removes IsNot Nothing Then

            For Each item As T In _removes

               _list.Remove(item)

            Next

            _removes = Nothing

         End If

      End Sub

 

      Public Sub Rollback()

         _adds = Nothing

         _removes = Nothing

      End Sub

 

 

      Public Sub Dispose() Implements IDisposable.Dispose

         Commit()

         GC.SuppressFinalize(Me)

      End Sub

 

   End Class

 

End Module

Filed under: , , , ,

Comments

# re: List transactions

Thursday, December 18, 2008 11:09 AM by MartinJ

There's an edge case that will fail.

Add(objA)

Remove(objA)

Add(objA)

Commit

objA will not be in the list after Commit is called. The best way that I can think to do this is to maintain two lists. One contains the committed items. The other contains the working list. Commit will replace the commited list with the working one. Rollback will replace the working one with the commited list.

This could get tricky.

# re: List transactions

Thursday, December 18, 2008 8:40 PM by bill

Hi Martin,

Try it. You'll find OjbA is in the list, at least with List(Of T).

If you had a unique list, then this might be a problem.

# Bill McCarthy: List transactions&hellip; &laquo; notgartner

Tuesday, December 30, 2008 9:14 PM by Bill McCarthy: List transactions… « notgartner

Pingback from  Bill McCarthy: List transactions… « notgartner

# re: List transactions

Tuesday, December 30, 2008 10:36 PM by andy

You could get around the add/remove/add problem by maintaining a single list of actions (i.e. merging _adds and _removes together) and then Commit performing them in order.

It might be nice to support Contains too; in fact having ListTransaction implement IList would be really cool.

# An alternate version

Tuesday, December 30, 2008 11:05 PM by andy

Imports System.Collections.Generic

Module ListExtensions

   <Runtime.CompilerServices.Extension()> _

   Function GetTransaction(Of T)(ByVal list As IList(Of T)) As ListTransaction(Of T)

       Return New ListTransaction(Of T)(list)

   End Function

   Public Class ListTransaction(Of T)

       Implements IDisposable, IList(Of T)

       Private _list As IList(Of T)

       Private _clone As List(Of T)

       Public Sub New(ByVal list As IList(Of T))

           _list = list

       End Sub

       Public Sub Add(ByVal item As T) Implements ICollection(Of T).Add

           If _clone Is Nothing Then _clone = New List(Of T)(_list)

           _clone.Add(item)

       End Sub

       Public Function Remove(ByVal item As T) As Boolean Implements ICollection(Of T).Remove

           If _clone Is Nothing Then _clone = New List(Of T)(_list)

           Return _clone.Remove(item)

       End Function

       Public Sub Clear() Implements ICollection(Of T).Clear

           If _clone Is Nothing Then

               _clone = New List(Of T)

           Else

               _clone.Clear()

           End If

       End Sub

       Public Function Contains(ByVal item As T) As Boolean Implements ICollection(Of T).Contains

           If _clone Is Nothing Then

               Return _list.Contains(item)

           Else

               Return _clone.Contains(item)

           End If

       End Function

       Public Sub CopyTo(ByVal array() As T, ByVal arrayIndex As Integer) Implements ICollection(Of T).CopyTo

           If _clone Is Nothing Then

               _list.CopyTo(array, arrayIndex)

           Else

               _clone.CopyTo(array, arrayIndex)

           End If

       End Sub

       Public ReadOnly Property Count() As Integer Implements ICollection(Of T).Count

           Get

               If _clone Is Nothing Then

                   Return _list.Count

               Else

                   Return _clone.Count

               End If

           End Get

       End Property

       Public ReadOnly Property IsReadOnly() As Boolean Implements ICollection(Of T).IsReadOnly

           Get

               Return _list.IsReadOnly

           End Get

       End Property

       Public Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator

           If _clone Is Nothing Then

               Return _list.GetEnumerator

           Else

               Return _clone.GetEnumerator

           End If

       End Function

       Private Function mGetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator

           If _clone Is Nothing Then

               Return DirectCast(_list, System.Collections.IEnumerable).GetEnumerator

           Else

               Return DirectCast(_clone, System.Collections.IEnumerable).GetEnumerator

           End If

       End Function

       Public Function IndexOf(ByVal item As T) As Integer Implements IList(Of T).IndexOf

           If _clone Is Nothing Then

               Return _list.IndexOf(item)

           Else

               Return _clone.IndexOf(item)

           End If

       End Function

       Public Sub Insert(ByVal index As Integer, ByVal item As T) Implements IList(Of T).Insert

           If _clone Is Nothing Then _clone = New List(Of T)(_list)

           _clone.Insert(index, item)

       End Sub

       Default Public Property Item(ByVal index As Integer) As T Implements IList(Of T).Item

           Get

               If _clone Is Nothing Then

                   Return _list.Item(index)

               Else

                   Return _clone.Item(index)

               End If

           End Get

           Set(ByVal value As T)

               If _clone Is Nothing Then _clone = New List(Of T)(_list)

               _clone.Item(index) = value

           End Set

       End Property

       Public Sub RemoveAt(ByVal index As Integer) Implements IList(Of T).RemoveAt

           If _clone Is Nothing Then _clone = New List(Of T)(_list)

           _clone.RemoveAt(index)

       End Sub

       Public Sub Commit()

           If _clone Is Nothing Then Return

           _list.Clear()

           Dim genericList As List(Of T) = TryCast(_list, List(Of T))

           If genericList IsNot Nothing Then

               genericList.Capacity = _clone.Count

           End If

           For Each loItem As T In _clone

               _list.Add(loItem)

           Next

       End Sub

       Public Sub Rollback()

           _clone = Nothing

       End Sub

       Public Sub Dispose() Implements IDisposable.Dispose

           Commit()

           GC.SuppressFinalize(Me)

       End Sub

   End Class

End Module

# re: List transactions

Thursday, January 01, 2009 10:18 PM by silky

Nice idea, but calling 'Commit' on Dispose is pretty bad form. Commit should be explicitly called, like in every other transactional scenario.

Also worth noting it's not thread-safe.