DataGridView binding with abstract base classes
I recently ran into one of the most frustrating issues to face someone debugging a released application: a bug which only appears intermittently. In the instance, I have a collection of business objects that are being bound to a DataGridView. Periodically, the grid would throw repeated TargetInvocationExceptions when being bound.
In my case, the collection contained both objects directly implementing a base class and other objects inheriting from the base class and extending upon it. I found that if the first item in the list was in instance of the base class, the grid would render correctly. However, if the first item was a special instance type inheriting from this base class, the DataGridView would bomb out. I suspected the framework infers the type of the first object and tries to cast any subsequent objects to this specific type. It does not fall back to a base class type.
I confirmed my suspicions by testing a collection of objects inheriting from an abstract base class. In the code below, I’m using an abstract base class of Animal. From this, I have two implementing classes: Dog and Cat.
Public MustInherit Class Animal
Private _numFeet As Integer
Private _name As String
Public Property numFeet() As Integer
Get
Return _numFeet
End Get
Set(ByVal value As Integer)
_numFeet = value
End Set
End Property
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Overridable ReadOnly Property PetType() As String
Get
Return "Unknown"
End Get
End Property
Public Sub New()
End Sub
Public Sub New(ByVal petName As String, ByVal numFeet As Integer)
_name = petName
_numFeet = numFeet
End Sub
End Class
Public Class Dog
Inherits Animal
Public Sub New(ByVal petName As String, ByVal numFeet As Integer)
MyBase.New(petName, numFeet)
End Sub
Public Overrides ReadOnly Property PetType() As String
Get
Return "Dog"
End Get
End Property
End Class
Public Class Cat
Inherits Animal
Public Sub New(ByVal petName As String, ByVal numFeet As Integer)
MyBase.New(petName, numFeet)
End Sub
Public Overrides ReadOnly Property PetType() As String
Get
Return "Cat"
End Get
End Property
End Class
I then created a collection of “animals” as follows:
Public Class PetList
Inherits CollectionBase
Implements System.ComponentModel.IBindingList
Public Sub Add(ByVal pet As Animal)
List.Add(pet)
End Sub
#Region " IBindingList Implementation "
‘Implementation omitted for brevity. This must be implemented for binding purposes
#End Region
End Class
The form implementation is rather simple. Add a DataGridView to the form and the following code on the Form Load event:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim animalList As New PetList
animalList.Add(New Cat("Fluffy", 4))
animalList.Add(New Dog("Woofie", 4))
Me.PetCollectionBindingSource.DataSource = DirectCast(animalList, System.ComponentModel.IBindingList)
End Sub
By default, this code should error when rendering the second pet, the Dog, because it can’t cast Dog as a Cat, rather than trying to cast Dog and Cat as Animal. To fix the problem, you need to strongly type the collection. I have found 3 workarounds to solve the problem.
1) Replace the PetList with a collection that from System.ComponentModel.BindingList(Of Animal)
2) Use a strongly typed Item accessor as follows:
Public ReadOnly Property Item(ByVal index As Integer) As Animal
Get
Return Me.InnerList.Item(index)
End Get
End Property
3) Implement System.ComponentModel.ITypedList as follows:
Public Function GetItemProperties(ByVal listAccessors() As System.ComponentModel.PropertyDescriptor) As System.ComponentModel.PropertyDescriptorCollection Implements System.ComponentModel.ITypedList.GetItemProperties
Return System.ComponentModel.TypeDescriptor.GetProperties(GetType(Animal))
End Function
Public Function GetListName(ByVal listAccessors() As System.ComponentModel.PropertyDescriptor) As String Implements System.ComponentModel.ITypedList.GetListName
Return "Animal"
End Function
Hopefully if you have run into this insidious error, the above information can help. Also, feel free to vote on this item at the feedback center: http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=7849a669-fe1e-469d-b02e-b36f3191c65d. I have included a sample application in that feedback item as well since I can’t upload sample projects to this blog at the moment.