Why does iterating a Hashtable with `For Each` not work in VBScript?
为什么可以使用
1 2 3 4 5 6 7 | Dim i For Each i In CreateObject("System.Collections.ArrayList") ' no error Next For Each i In CreateObject("System.Collections.Hashtable") ' error Next |
迭代
Object doesn't support this property or method.
脚本语言有一个技术限制,它们只能使用 coclass 的默认接口。他们根本没有接口的概念,也没有通过 IUnknown::QueryInterface() 获取另一个接口的后门。就像您可以在 C# 中通过转换为所需的接口类型一样。 ArrayList 的迭代器如下所示:
1 2 3 | private sealed class ArrayListEnumeratorSimple : IEnumerator, ICloneable { // etc... } |
IEnumerator 是默认界面,您可以在 VBScript 中使用它。但是,Hashtable 的枚举器如下所示:
1 2 3 | private class HashtableEnumerator : IDictionaryEnumerator, IEnumerable, ICloneable { // etc.. } |
IDictionaryEnumerator 是默认值,而不是 IEnumerable。因此 VBScript 找不到所需的 Current 和 MoveNext 成员。只有Entry、Key和Value,它们是无用的。 Keys and Values 集合也差不多:
1 2 3 | public class KeysCollection : ICollection, IEnumerable { // etc.. } |
同样的问题,CopyTo、Count、IsSynchronized 和 SyncRoot 都没用。通过将 [ComDefaultInterface] 属性应用于这些类,Microsoft 可以很容易地解决此问题。但他们没有。
这可以解决。需要的是可以 QI 默认接口以获得 IEnumerable 接口的代码。你可以帮助一个小的 C# 类库项目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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; } } } |
使用 32 位和 64 位版本的 Regasm 构建和注册程序集。现在你可以让这个脚本工作了:
1 2 3 4 5 6 7 | 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 |
输出:
1 2 3 4 5 | Microsoft (R) Windows Script Host Version 5.812 Copyright (C) Microsoft Corporation. All rights reserved. 1: one 2: two |