C#でVBAから列挙できるCOMオブジェクトを作成する

#はじめに
C#でCOMコンポーネントを作成し、Microsoft Office のマクロでオブジェクトを列挙したいな ([For Each ... Next](https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/for-eachnext-statement)) と思い立って、調べてみました。

#COM のコード
「.NET クラスライブラリ」としてプロジェクトを作成し、アプリケーション＞アセンブリ情報の「[アセンブリをCOM参照可能にする](https://docs.microsoft.com/ja-jp/dotnet/framework/interop/packaging-an-assembly-for-com)」とビルド＞出力の「[COM相互運用機能の登録](https://docs.microsoft.com/ja-jp/dotnet/framework/interop/registration-free-com-interop)」を有効にします。GUID 値はツールで生成します。管理者として Visual Studio を実行してビルドすれば、その過程で COM を登録してくれます。尤も「COM コンポーネントをC#で作成しよう」と考えておられるプロフェッショナルの皆様には、COM の細かな説明を語る必要はないでしょう...


```C#
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace MyCOM
{
    [Guid("1???????-????-????-????-????????????")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IPerson
    {
        string FirstName { get; }
        string LastName { get; }
        int Age { get; }
        string GetInfo();
    }

    [Guid("2???????-????-????-????-????????????")]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(IPerson))]
    public class Person : IPerson
    {
        public string FirstName { get; }
        public string LastName { get; }
        public int Age { get; }

        public Person(string firstName, string lastName, int age)
        {
            FirstName = firstName;
            LastName = lastName;
            Age = age;
        }

        public string GetInfo() {
            return string.Format("{0} {1} ({2})", FirstName, LastName, Age);
        }
    }

    [Guid("3???????-????-????-????-????????????")]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)]
    public interface IPersons : IEnumerable
    {
        new IEnumerator GetEnumerator();
    }

    [Guid("4???????-????-????-????-????????????")]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(IPersons))]
    public class Persons : IPersons
    {
        System.Collections.Generic.List<Person> _data = new List<Person>();
        public Persons()
        {
            // SAMPLE DATA
            _data.Add(new Person("なまえ", "みょうじ", 20));
            _data.Add(new Person("名前", "苗字", 21));
        }

        public IEnumerator GetEnumerator()
        {
            return _data.GetEnumerator();
        }
    }
}
```

##出来たオブジェクトの型

![image.png](https://qiita-image-store.s3.amazonaws.com/0/100206/2b31cf9a-d641-5350-9b5d-7a84f0dcaa4b.png)
[System.Collections.IEnumerator](https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.ienumerator)（非ジェネリックのほう）は、[IEnumVARIANT](https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.interopservices.comtypes.ienumvariant)を自動的にインプリメントしてくれるようです。

#利用例
エクセルやワードのマクロから呼び出してみます。参照設定を忘れずに...

```VB
Sub Sample()
'
' Sample Macro
'
'
    Dim persons As New MyCOM.persons
    Dim item As MyCOM.Person

    For Each item In persons
        MsgBox item.GetInfo()
    Next
End Sub

```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/100206/0ac2616a-7b89-6ac1-ff25-44cb10d97e7c.png)


#参考サイト（感謝！）
- [Exposing an Enumerator from Managed Code to COM](https://limbioliong.wordpress.com/2011/10/28/exposing-an-enumerator-from-managed-code-to-com/) - LIM BIO LIONG さん
- [オートメーション / コレクションオブジェクト](http://eternalwindows.jp/com/auto/auto05.html) - EternalWindows