Я изо всех сил пытаюсь выяснить, как убедить VBA сделать For Each для класса, который упаковывает библиотеку C#, которую я написал.
Библиотека C# сама является оболочкой для OrderedDictionary и GetEnumerator, который я реализовал в классе C#, прекрасно работает с VBA. Я могу сделать For Each на простых экземплярах библиотеки C# без проблем.
Однако ограничение C#, которое хорошо задокументировано, состоит в том, что он не может правильно реализовать .Item (Key) и, следовательно, вам придется прибегнуть к методам getitem и setitem.
Чтобы обойти эту проблему, а также набрать экземпляры класса C#, я создаю оболочки класса VBA. Проблема, с которой я сталкиваюсь, заключается в том, как включить For Each Loops для этих классов-оболочек.
В классе-оболочке VBA у меня есть код ниже, где s.HostKvp - это экземпляр библиотеки C# Kvp.
NB Атрибут enum устанавливается с помощью аннотации @Dnumerator RubberDuck.
'@Enumerator
Public Property Get NewEnum() As IUnknown
Set NewEnum = s.HostKvp.NewEnum // the function in the c# library is 'NewEnum' and not '[_NewEnum]'
End Property
В библиотеке C# у меня есть
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[DispId(-4)]
public stdole.IUnknown NewEnum()
{
return (stdole.IUnknown) GetEnumerator();
}
public IEnumerator GetEnumerator()
{
foreach (KeyValuePair<dynamic, dynamic> myPair in MyKvp)
{
yield return new KVPair(myPair.Key, myPair.Value);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
и в интерфейсе
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[DispId(-4)]
stdole.IUnknown NewEnum();
//[DispId(-4)]
IEnumerator GetEnumerator();
Когда я пытаюсь выполнить итерацию экземпляра Wrapped, я получаю сообщение об ошибке:
Unable to cast object of type '<GetEnumerator>d__67' to type 'stdole.IUnknown'.
При чтении, которое я сделал, я наткнулся на IEnumVARIANT и проверил, заменив ' stdole.IUnknown 'от System.Runtime.InteropServices.ComTypes.IEnumVARIANT', но затем получаю ошибку
Unable to cast object of type '<GetEnumerator>d__67' to type 'System.Runtime.InteropServices.ComTypes.IEnumVARIANT'.
Я также пытался заменить 'stdole.IUnknown' на 'KVPair' с похожими результатами.
Я чувствую себя не в своей тарелке, поэтому буду признателен за указатель того, что я должен прочитать, чтобы получить KVPair, созданный GetEnumerator, в NewEnum в VBA.
У меня есть обходной путь для этого вопроса, который я s для реализации методов Keys и Values класса C# в оболочке VBA.
Редактировать 06 марта 2020
Я изо всех сил стараюсь следовать предоставленным комментариям, поэтому добавляю больше информация, чтобы попытаться прояснить мою проблему.
класс C# предоставляет объект Kvp (Key Value Pair). Закрытый член, который предоставляет словарь хоста в коде c#:
private OrderedDictionary MyKvp = new OrderedDictionary();
В VBA отлично работает следующее.
Dim myKvp as Kvp: Set myKvp = New Kvp
myKvp.AddbyIndexFromArray Array(1,True, 5.0, "Hello World")
Dim myItem as Variant
For Each myItem in myKvp
Debug.Print myItem
Next
Создает ожидаемый вывод
1
True
5.0
Hello World.
Как видите, класс Kvp не является строго типизированным. Чтобы добиться строгой типизации, я использую классы-обертки в VBA. Приведенный ниже код является примером простого класса-обертки.
Option Explicit
Private Type State
' Key:Long by Value:String -> "L88,R67,U89 etc"
HostKvp As Kvp
End Type
Private s As State
Private Sub Class_Initialize()
Set s.HostKvp = New Kvp
End Sub
'@DefaultMember
Public Property Get Item(ByVal Key As Long) As String
Item = s.HostKvp.Item(Key)
End Property
Public Property Let Item(ByVal Key As Long, ByVal Value As Long)
s.HostKvp.Item(Key) = Value
End Property
'@Enumerator
Public Function NewEnum() As IUnknown
Set NewEnum = s.HostKvp.NewEnum
End Function
' Many other method wrappers not reproduced here
Проблема, с которой я столкнулся, заключается в том, как кодировать часть NewEnum кода c# .
метод, описанный выше
public stdole.IUnknown NewEnum()
, не работает, и ни на одну из моих интерпретаций рекомендаций, предложенных в комментариях.
Будем признательны за дальнейшие комментарии.
Также может помочь, если я посоветую читателям, что я не профессиональный программист, я буду заниматься VBA, помогая писать и обрабатывать технические документы. Стимулом для моего класса c# было предоставление более простого способа получения информации в словаре и простых задач, например, какой ключ соответствует этому значению et c.
Второе редактирование 6 марта 2020 г. Использование обертки Test класс
Class KvpEnumTest
Option Explicit
Private Type State
HostKvp As KvpOD
End Type
Private s As State
Private Sub Class_Initialize()
Set s.HostKvp = New KvpOD
End Sub
'@DefaultMember
Public Property Get Item(ByVal Key As Long) As String
Item = s.HostKvp.FromKey(Key)
End Property
Public Property Let Item(ByVal Key As Long, ByVal Value As String)
s.HostKvp.ToKeyKey Key, Value
End Property
Public Sub AddByIndex(ByVal Value As String)
s.HostKvp.AddByIndex Value
End Sub
Public Sub AddByIndexFromArray(ByVal Value As Variant)
s.HostKvp.AddByIndexFromArray Value
End Sub
Public Function Keys() As Variant
Keys = s.HostKvp.GetKeys
End Function
Public Function Values() As Variant
Values = s.HostKvp.GetValues
End Function
'@Enumerator
Public Property Get NewEnum() As Variant
Set NewEnum = s.HostKvp.KvpEnum
End Property
In the VBA wrapper class I have
'@Enumerator
Public Property Get NewEnum() As Variant
set NewEnum = s.HostKvp.KvpEnum
End Property
и тестовый код
Public Sub TestKvpEnumTest()
Dim myTest As KvpEnumTest
Set myTest = New KvpEnumTest
myTest.AddByIndexFromArray Split("Hello there world its a nice day")
Dim myKeys As Variant
myKeys = myTest.Keys
' myKeys is visible in the locales window as an Array(0 to 6) of Variant/Long
Dim myValues As Variant
myValues = myTest.Values
' myValues is visible in the locales window as an Array(0 to 6) of Variant/String
Dim myItem As Variant
For Each myItem In myTest
Debug.Print myItem
Next
End Sub
В коде c# я попробовал следующее для
public IEnumerable KvpEnum
{
get; set;
}
Значение, возвращаемое VBA: Вариант / Объект со значением Nothing. и последующее сообщение об ошибке
451 Свойство let процедуры не определено, а свойство get процедуры не вернуло объект
с
public IEnumerable KvpEnum
{
get
{
foreach (DictionaryEntry myPair in MyKvp)
{
yield return new KVPair(myPair.Key, myPair.Value);
}
}
set
{ }
}
У меня есть точка останова на Get и прикрепить к текстовому процессу, но точка останова не сработала. В VBA я получаю возвращенное значение Variant / Object / Object, которое является пустым, и то же самое сообщение об ошибке
451 Свойство let, процедура не определена, и свойство get, процедура не возвратила объект
с
public IEnumerable KvpEnum()
{
foreach (DictionaryEntry myPair in MyKvp)
{
yield return new KVPair(myPair.Key, myPair.Value);
}
}
С снова теми же результатами
with public IEnumerator KvpEnum()
{
foreach (DictionaryEntry myPair in MyKvp)
{
yield return new KVPair(myPair.Key, myPair.Value);
}
}
Err 13 Несоответствие типов
с
public IEnumerable KvpEnum()
{
return (IEnumerable)GetEnumerator();
}
error & 80004002 Невозможно привести объект типа 'd__64' для ввода 'System.Collections. IEnumerable '.
Но (ура, ура) наконец-то
public IEnumerator KvpEnum()
{
return GetEnumerator();
}
Дает правильный результат.
Это обнаружение зависело также от размещения в настройках интерфейса Class и Com .
с интерфейсом
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
и классом
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
все работает как надо, за исключением того факта, что если я возвращаю KVPair из KpEnum, мне нужно его разыграть в переменную KVPair в VBA, прежде чем я смогу получить доступ к ключу и значению. Если я использую For Each myItem, я получаю ошибку, необходимую для объекта 424.
Но MS не рекомендует использовать AutoDual в classinterface из-за проблем с версиями.
с
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
Я получаю ошибку при вызове метода .AddByIndexFromArray
& 1B6 Объект не поддерживает это свойство или метод
с
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
Я получаю ошибку
& DD Несоответствие типов
для управляющей переменной myItem, но снова, если я приведу myPair к объекту KVPar
Я получу доступ к .Key и .Value
Итак для повторения в c# коде
public IEnumerator KvpEnum()
{
return GetEnumerator();
}
public IEnumerator GetEnumerator()
{
foreach (DictionaryEntry myPair in MyKvp)
{
yield return (dynamic)new KVPair(myPair.Key, myPair.Value);
}
}
все работает, за исключением того, что мне приходится приводить переменную управления MyItem к KVPair, даже если вдова местных жителей говорит, что myItem является Variant / KVPair
Если я возвращаю примитив (т. Е. Просто ключ), используя
public IEnumerator KvpEnum()
{
return GetEnumerator();
}
public IEnumerator GetEnumerator()
{
foreach (DictionaryEntry myPair in MyKvp)
{
yield return (dynamic)myPair.Key;
}
}
, тогда я могу получить доступ к значению управляющей переменной myItem без необходимости приведения.
В заключение моя проблема решена , но я рад услышать любые комментарии по поводу Настройки интерфейса com и class.