Why can one iterate an ArrayList
using For Each
but not a Hashtable
?
Dim i
For Each i In CreateObject("System.Collections.ArrayList") ' no error
Next
For Each i In CreateObject("System.Collections.Hashtable") ' error
Next
Iterating the HashTable
gives
Object doesn't support this property or method.
Scripting languages have a technical limitation, they can only use the default interface of a coclass. They have no notion of interfaces at all and no back-door to obtain another interface through IUnknown::QueryInterface(). Like you can in C# by casting to the desired interface type. The iterator for ArrayList looks like this:
private sealed class ArrayListEnumeratorSimple : IEnumerator, ICloneable {
// etc...
}
IEnumerator is the default interface, you have no problem using it from your VBScript. However, the enumerator for Hashtable looks like this:
private class HashtableEnumerator : IDictionaryEnumerator, IEnumerable, ICloneable {
// etc..
}
IDictionaryEnumerator is the default, not IEnumerable. So VBScript cannot find the required Current and MoveNext members. Only Entry, Key and Value, they are useless. Much the same for the Keys and Values collection:
public class KeysCollection : ICollection, IEnumerable {
// etc..
}
Same problem, CopyTo, Count, IsSynchronized and SyncRoot are useless. Microsoft could have very easily fixed this problem by applying the [ComDefaultInterface] attribute to these classes. But they didn't.
This can be worked around. What is required is code that can QI the default interface to obtain the IEnumerable interface. You can help with a wee C# class library project:
using System;
using System.Collections;
using System.Runtime.InteropServices;
namespace VBScript
{
[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IMapper {
IEnumerable ToEnum(object itf);
}
[ComVisible(true), ProgId("VBScript.Mapper")]
public class Mapper : IMapper {
public IEnumerable ToEnum(object itf) {
return (IEnumerable)itf;
}
}
}
Build and register the assembly with both the 32-bit and 64-bit version of Regasm. Now you can make this script work:
Set table = CreateObject("System.Collections.Hashtable")
table.Add 1, "one"
table.Add 2, "two"
Set mapper = CreateObject("VBScript.Mapper")
For Each key in mapper.ToEnum(table.Keys)
WScript.Echo key & ": " & table(key)
Next
Output:
Microsoft (R) Windows Script Host Version 5.812
Copyright (C) Microsoft Corporation. All rights reserved.
1: one
2: two
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With