Как реализовать NewEnum в классе VBA, который включает библиотеку C# - PullRequest
4 голосов
/ 04 марта 2020

Я изо всех сил пытаюсь выяснить, как убедить 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.

...