ArrayList Strongly Typed

D

David

David,

Can you define & give a VB example of "every day" needing strong typing in a
For Each enumeration.

Sure, where MyCollection is a subclass of CollectionBase that holds
Widgets

For each value as String in MyCollection

still compiles. As I said, shadowing GetEnumerator can help alleviate,
but not eliminate the problem.


I can think of one example, however its a
performance/boxing thing which is needed in less then 20% of my code...


How do you see an overridable GetEnumerator providing strong typing?

Umm, because the default GetEnumerator returns an untyped IEnumerator
interface. I honestly don't understand what you're not seeing here.
That's not snarky, I honestly don't understand.
Based
on the performance/boxing requirement I can see an explicit interface
implementation & a strongly typed Enumerator class, again less then 20% of
my code (in fact I have yet to need this optimization).

Nothing I've said has anything to do with performance optimization
(except for a very quick sidebar mention of value type collections a few
posts ago).

Going back to your original post:

I am trying to figure out what & how this "huge hole in the type system" you
perceive is, so that possible I or someone else can correct you. As quite
truthfully I don't see any real holes in CollectionBase, especially when you
overload the CollectionBase.On* methods. Granted you may have runtime errors
as opposed to compile time errors

Okay, I have no idea how to respond to this, since the whole impetus
behind strong typing is so that we can catch errors at compile time
rather than runtime.

Although, when I think about it a little more, I guess this explains
what I didn't understand above. "Strongly typed" has different
definitions (see wikipedia). To me, if a language or feature associates
type with the value but not the variable, that's weakly typed. VBScript
is weakly typed by that definition, although by your definition VBScript
is still strongly typed where COM objects are concerned since they are
typed at runtime. In your words, you may have runtime errors rather
than compile time errors.

If that's your definition though, then we need another term for this
discussion, because I usually want typing that's stronger than that. In
my experience, "strongly typed" in the .Net world usually refers to
compile-time type checking. In fact, didn't you say something just a
few posts ago about ArrayList not being strongly typed, or was that
somebody else?
, however you run that risk (runtime
errors) when using generalized interfaces as IList in any class that
implements IList.

And IList is untyped. Everything's an Object to IList, and so the
compiler can't enforce typing. And if I inherit from CollectionBase I
must accept this and other untyped interfaces, even if I don't want
them. And I seldom do want them unless I'm dealing with certain UI
controls.

I consider IEnumerator more of a problem than IList since the user will
always have to cast things like IList.Item. So I know the user has to
be explicit when the type system is broken, which is finen.
IEnumerator, though, can break strong typing without a cast because of
the way the language works.

Anyway, you seem to have your hackles up, and I have no idea why. I'm
not dissing the writers of CollectionBase here. It's a very useful
class, and given the lack of templates or generics they had little
choice but to allow weakly typed access. Virtual validators were a
pretty clever answer to this problem, but they aren't strong typing.
 
J

Jay B. Harlow [MVP - Outlook]

David,
OK, I follow what you are saying.

If strongly typing the Enumerator was important to me. I would define my own
CollectionBase class (ala my CollectionBaseEx in this thread) that used
explicit interface implementation to hide the base GetEnumerator, avoiding
Shadows. Then in the derived collection I would add an explicitly typed
GetEnumerator, which returned a strongly typed Enumerator object. Both the
collection & enumerator would be based on base classes that contained
duplicate code to avoid duplicated code as much as possible...

Something like:

Public Class WidgetCollection
Inherits CollectionBaseEx

Public Sub Add(ByVal value As Widget)
MyBase.InnerList.Add(value)
End Sub

Default Public ReadOnly Property Item(ByVal index As Integer) As Widget
Get
Return DirectCast(MyBase.InnerList.Item(index), Widget)
End Get
End Property

Public Function GetEnumerator() As WidgetEnumerator
Return New WidgetEnumerator(Me.InnerList.GetEnumerator())
End Function

End Class

Public Class WidgetEnumerator
Inherits EnumeratorBase

Friend Sub New(ByVal enumerator As IEnumerator)
MyBase.New(enumerator)
End Sub

Public ReadOnly Property Current() As Widget
Get
Return DirectCast(enumerator.Current, Widget)
End Get
End Property

End Class

Public Class EnumeratorBase
Implements IEnumerator

Private ReadOnly m_enumerator As IEnumerator

Protected Sub New(ByVal enumerator As IEnumerator)
If enumerator Is Nothing Then
Throw New ArgumentNullException("enumerator")
End If
m_enumerator = enumerator
End Sub

Protected ReadOnly Property Enumerator() As IEnumerator
Get
Return m_enumerator
End Get
End Property

Private ReadOnly Property IEnumerator_Current() As Object Implements
IEnumerator.Current
Get
Return m_enumerator.Current
End Get
End Property

Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
Return m_enumerator.MoveNext()
End Function

Public Sub Reset() Implements IEnumerator.Reset
m_enumerator.MoveNext()
End Sub

End Class

<Serializable()> _
Public MustInherit Class CollectionBaseEx
Implements IList

Private ReadOnly m_list As IList

Protected Sub New()
MyClass.New(New ArrayList)
End Sub

Protected Sub New(ByVal list As IList)
If list Is Nothing Then
Throw New ArgumentNullException("list")
End If
m_list = list
End Sub

Protected ReadOnly Property InnerList() As IList
Get
Return m_list
End Get
End Property

Protected ReadOnly Property List() As IList
Get
Return Me
End Get
End Property

Public ReadOnly Property Count() As Integer Implements ICollection.Count
Get
Return m_list.Count
End Get
End Property

Public Sub Clear() Implements IList.Clear
Me.OnClear()
m_list.Clear()
Me.OnClearComplete()
End Sub

Public Sub RemoveAt(ByVal index As Integer) Implements IList.RemoveAt
Dim value As Object = m_list(index)
Me.OnValidate(value)
Me.OnRemove(index, value)
m_list.RemoveAt(index)
Me.OnRemoveComplete(index, value)
End Sub

#Region " IList support "

Private ReadOnly Property IList_IsSynchronized() As Boolean Implements
ICollection.IsSynchronized
Get
Return m_list.IsSynchronized
End Get
End Property

Private ReadOnly Property IList_SyncRoot() As Object Implements
ICollection.SyncRoot
Get
Return m_list.SyncRoot
End Get
End Property

Private ReadOnly Property IList_IsFixedSize() As Boolean Implements
IList.IsFixedSize
Get
Return m_list.IsFixedSize
End Get
End Property

Private ReadOnly Property IList_IsReadOnly() As Boolean Implements
IList.IsReadOnly
Get
Return m_list.IsReadOnly
End Get
End Property

Private Function IList_Add(ByVal value As Object) As Integer Implements
IList.Add
Dim index As Integer = m_list.Count
Me.OnValidate(value)
Me.OnInsert(index, value)
Return m_list.Add(value)
Me.OnInsertComplete(index, value)
End Function

Private Sub IList_CopyTo(ByVal array As System.Array, ByVal index As
Integer) Implements ICollection.CopyTo
m_list.CopyTo(array, index)
End Sub

Private Function IList_Contains(ByVal value As Object) As Boolean
Implements IList.Contains
Return m_list.Contains(value)
End Function

Private Function IList_GetEnumerator() As IEnumerator Implements
IList.GetEnumerator
Return m_list.GetEnumerator
End Function

Private Function IList_IndexOf(ByVal value As Object) As Integer
Implements IList.IndexOf
Return m_list.IndexOf(value)
End Function

Private Sub IList_Insert(ByVal index As Integer, ByVal value As Object)
Implements IList.Insert
Me.OnValidate(value)
Me.OnInsert(index, value)
m_list.Insert(index, value)
Me.OnInsertComplete(index, value)
End Sub

Private Property IList_Item(ByVal index As Integer) As Object Implements
IList.Item
Get
Dim currentValue As Object = m_list.Item(index)
currentValue = Me.OnGet(index, currentValue)
Return currentValue
End Get
Set(ByVal value As Object)
Dim oldValue As Object = m_list(index)
Me.OnValidate(value)
Me.OnSet(index, oldValue, value)
m_list(index) = value
Me.OnSetComplete(index, oldValue, value)
End Set
End Property

Private Sub IList_Remove(ByVal value As Object) Implements IList.Remove
Dim index As Integer = m_list.IndexOf(value)
Me.OnValidate(value)
Me.OnRemove(index, value)
m_list.Remove(value)
Me.OnRemoveComplete(index, value)
End Sub

#End Region

#Region " Custom processing support "

Protected Overridable Sub OnClear()

End Sub

Protected Overridable Sub OnClearComplete()

End Sub

Protected Overridable Function OnGet(ByVal index As Integer, ByVal
currentValue As Object) As Object
Return currentValue
End Function

Protected Overridable Sub OnInsert(ByVal index As Integer, ByVal value
As Object)

End Sub

Protected Overridable Sub OnInsertComplete(ByVal index As Integer, ByVal
value As Object)

End Sub

Protected Overridable Sub OnRemove(ByVal index As Integer, ByVal value
As Object)

End Sub

Protected Overridable Sub OnRemoveComplete(ByVal index As Integer, ByVal
value As Object)

End Sub

Protected Overridable Sub OnSet(ByVal index As Integer, ByVal oldValue
As Object, ByVal newValue As Object)

End Sub

Protected Overridable Sub OnSetComplete(ByVal index As Integer, ByVal
oldValue As Object, ByVal newValue As Object)

End Sub

Protected Overridable Sub OnValidate(ByVal value As Object)

End Sub

#End Region

End Class

The above sample causes a compile error in VS.NET 2003 with your sample!
For each value as String in MyCollection

VS.NET 2005 (aka Whidbey, due out later in 2005) should simplify defining
Strongly typed collections & enumerators via the new Generics support.

Hope this helps
Jay
 
J

Jay B. Harlow [MVP - Outlook]

Doh!
Both the collection & enumerator would be based on base classes that
contained duplicate code to avoid duplicated code as much as possible...
That's poorly worded. Should read:

Both the collection & enumerator would be based on base classes that
contained common code to all collections & enumerators to avoid duplicated
code as much as possible...

And I agree the "real" problem is the way IEnumerator & IList are defined as
they are defined very generalized to ensure maximum polymorphism...

Hope this helps
Jay
 
D

David

David,
OK, I follow what you are saying.

If strongly typing the Enumerator was important to me. I would define my own
CollectionBase class (ala my CollectionBaseEx in this thread) that used
explicit interface implementation to hide the base GetEnumerator, avoiding
Shadows. Then in the derived collection I would add an explicitly typed
GetEnumerator, which returned a strongly typed Enumerator object.

Like I said, at one point I thought this would really bother me but in
practice it turns out, not so much. Although, and this surprised me, it
seems I do have a shadowed GetEnumerator in my typed collection classes.
I must have added it last fall when I moved my generator out of
CodeSmith.

I might be missing something, but I don't see what the classes below buy
me beyond what CollectionBase offers. What invalid code fails on your
classes but still compiles on a CollectionBase subclass with a shadowed
GetEnumerator()?




Both the
 
J

Jay B. Harlow [MVP - Outlook]

David,
I might be missing something, but I don't see what the classes below buy
me beyond what CollectionBase offers. What invalid code fails on your
classes but still compiles on a CollectionBase subclass with a shadowed
GetEnumerator()?
Three things that I know of:
- It avoid the Shadows, as I stated elsewhere in this thread Shadows is
really intended for Versioning
- More importantly it prevents you from casting the class into a
CollectionBase variable & using the base CollectionBase.GetEnumerator. The
IList.GetEnumerator is available as expected.
- Most importantly it allows you to use any IList implementation instead of
just ArrayList

See my other post in this thread where I explain CollectionBaseEx &
DictionaryBaseEx.

Hope this helps
Jay
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top