読者です 読者をやめる 読者になる 読者になる

C++/CLIで、genericなCollectionを自力で実装してみる

.NET Frameworkには、Collectionというgeneric型のコレクションを作成するときに使える便利なクラスがある。このクラスは、書き込み方面の処理さえできれば問題ない場合には、

ClearItems
InsertItem
RemoveItem
SetItem

の4つのメソッドさえ実装すれば良いので、.NET 1.X時代のように大変な思いをしてCollectionを書く必要はない。非常に楽だ。
しかしながら、コレクションの値を参照されたときに初めて値を用意するといったLazy Load的な処理をしようとすると、このクラスは全く頼りにならない。読み込み方向のプロパティ(Item/default)が、

virtual property T default [int] {
  T get (int index) sealed;
  void set (int index, T value) sealed;
}

という風に定義されているからだ。くせ者なのはsealed。そうじゃなくても、基底クラスにキャストされると中が直接見えるのはちょっと不味い。
ということで、頼るモノがなくなってしまい、全部を自力で実装しなくてはならないことになる。MSDNライブラリを参照すると、Collectionは、

[SerializableAttribute] 
[ComVisibleAttribute(false)] 
generic<typename T>
public ref class Collection :
  IList<T>,
  ICollection<T>, 
  IEnumerable<T>,
  IList,
  ICollection,
  IEnumerable

という具合になっており、一見すると、実装しないといけないメソッドが死ぬほどありそうな予感。しかしながら、実は、IListがICollectionを継承したりという感じになっているので、本当に実装すべきメソッドはそんなに多くない。

ということで、とりあえずは、Listの単なるラッパーを実装してみることにしよう。これだけでも同じ事をしたい人にはかなり重要な指針になるはず。

generic<typename T>
public ref class CollectionImpl :
  public System::Collections::Generic::IList<T>,
  public System::IDisposable
{
public:
  CollectionImpl()
  {
    m_list = gcnew System::Collections::Generic::List<T>();
  }
  
  virtual ~CollectionImpl()
  {
  }
  
  !CollectionImpl()
  {
  }

  // IList<T>
  property T default[int]
  {
    virtual T get(int index)
    {
      return m_list[index];
    }
    virtual void set(int index, T value)
    {
      m_list[index] = value;
    }
  }
  
  virtual int IndexOf(T item)
  {
    return m_list->IndexOf(item);
  }
  
  virtual void Insert(int index, T item)
  {
    return m_list->Insert(index, item);
  }
  
  virtual void RemoveAt(int index)
  {
    return m_list->RemoveAt(index);
  }
  
  // ICollection<T>
  property int Count
  {
    virtual int get()
    {
      return m_list->Count;
    }
  }
  
  property bool IsReadOnly
  {
    virtual bool get()
    {
      return false; // we provides RW array
    }
  }
  
  virtual void Add(T item)
  {
    return m_list->Add(item);
  }
  
  virtual void Clear()
  {
    return m_list->Clear();
  }
  
  virtual bool Contains(T item)
  {
    return m_list->Contains(item);
  }
  
  virtual void CopyTo(array<T>^ arr, int arrIndex)
  {
    m_list->CopyTo(arr, arrIndex);
  }
  
  virtual bool Remove(T item)
  {
    return m_list->Remove(item);
  }
  
private:
  ref class CI_Enumerator sealed :
    public System::Collections::Generic::IEnumerator<T>
  {
  public:
    CI_Enumerator(CollectionImpl<T>^ col)
    {
      m_enum = col->m_list->GetEnumerator();
    }
    
    virtual ~CI_Enumerator()
    {
      delete m_enum;
      m_enum = nullptr;
    }
    
    !CI_Enumerator()
    {
    }
    
    property T Current
    {
      virtual T get() = System::Collections::Generic::IEnumerator<T>::Current::get
      {
        return m_enum->Current;
      }
    }
    
    virtual bool MoveNext()
    {
      return m_enum->MoveNext();
    }
    
    virtual void Reset()
    {
      return m_enum->Reset();
    }
  
  protected:
    virtual property Object^ Current_NoGeneric
    {
      // overrides non-generic version of IEnumerator::Current
      virtual Object^ get() = System::Collections::IEnumerator::Current::get
      {
        return Current;
      }
    }
  
  private:
    System::Collections::Generic::IEnumerator<T>^ m_enum;
  };

public:
  // IEnumerable<T>
  virtual System::Collections::Generic::IEnumerator<T>^ GetEnumerator()
  {
    return m_list->GetEnumerator();
  }

protected:
  // IEnumerable
  virtual System::Collections::IEnumerator^ GetEnumerator_NoGeneric() = System::Collections::IEnumerable::GetEnumerator
  {
    return m_list->GetEnumerator();
  }

private:
  System::Collections::Generic::List<T>^ m_list;
};

ちなみに、IDisposableを実装しているのは、僕の場合には、実は中身がstd::vectorになる予定だから。Listを使うだけならば必要はないので、そこと、~CollectionImpl (デストラクタ)を削除してください。