Maury Markowitz
Maury Markowitz

Reputation: 9279

IEnumerable is visible in COM, but not ICollection?

I have a bunch of Dictionary(Of Integer, SomeClass) in my main class, which I AutoDual for COM use. Users need to iterate over the instances in VBA (Excel specifically), so I have a bunch of these these in my main class...

Public ReadOnly Property Inflations() As IEnumerable
    Get
        Return InflationsDict.Values
    End Get
End Property

I can use this in my VBA code like this...

For Each I In CP.Inflations...

But that doesn't support Count. Dictionaries expose ICollection as well, which has .Count, but when I did this single change...

Public ReadOnly Property Inflations() As ICollection

the method disappears from COM, and that same For Each gets "Run-time error 438 Object doesn't support this property or method." It continues to work fine in VB.net.

I was told the basic rule is that non-generics should export OK. Nothing comes up during the compile - whereas when I tried exporting IEnumerable(Of Inflation) on a lark, sure enough a warning comes up in the output.

Is there a sidebar to the rule that applies in this case?

Upvotes: 3

Views: 356

Answers (1)

Hans Passant
Hans Passant

Reputation: 941397

I can tell I've been posting some misleading comments. The issue you are running into is that the ICollection members will be exposed but its inherited interface (IEnumerable) is not. Which breaks the ability to iterate the collection.

Let's write an interface for a dictionary. I'll assume variants:

<ComVisible(True)> _
<InterfaceType(ComInterfaceType.InterfaceIsDual)> _
Public Interface IMyDictionary
    ReadOnly Property Count As Integer
    Function Contains(ByVal key As Object) As Boolean
    <DispId(0)> Property Item(ByVal key As Object) As Object
    <DispId(-4)> Function GetEnumerator() As System.Collections.IEnumerator
End Interface

With a sample implementation:

<ComVisible(True)> _
<ClassInterface(ClassInterfaceType.None)> _
<ProgId("StackOverflow.Example")> _
Public Class Example
    Implements IMyDictionary

    Private collection As New Dictionary(Of Integer, String)

    Public Sub New()
        collection = New Dictionary(Of Integer, String)
        '' Add some sample data
        collection.Add(1, "one")
        collection.Add(2, "two")
        collection.Add(3, "three")
    End Sub

    Public ReadOnly Property Count As Integer Implements IMyDictionary.Count
        Get
            Return collection.Count
        End Get
    End Property

    Public Function GetEnumerator() As IEnumerator Implements IMyDictionary.GetEnumerator
        Return CType(collection.Values, System.Collections.IEnumerable).GetEnumerator()
    End Function

    Public Property Item(ByVal key As Object) As Object Implements IMyDictionary.Item
        Get
            Return collection(CInt(key))
        End Get
        Set(ByVal value As Object)
            collection(CInt(key)) = CStr(value)
        End Set
    End Property

    Public Function Contains(key As Object) As Boolean Implements IMyDictionary.Contains
        Return collection.ContainsKey(CInt(key))
    End Function
End Class

A sample VBScript that exercises it:

Set obj = CreateObject("StackOverflow.Example")
WScript.Echo "Count=" & obj.Count
WScript.Echo "Value for key 2=" & obj(2)
WScript.Echo "Contains(3)=" & obj.Contains(3)
WScript.Echo "Contains(42)=" & obj.Contains(42)
WScript.Echo "Values:"
For Each elem in obj
   WScript.Echo "  " & elem
Next

Output:

Count=3
Value for key 2=two
Contains(3)=True
Contains(42)=False
Values:
  one
  two
  three

Upvotes: 1

Related Questions