Wrapping MFC's list classes as IEnumerable<T> for .NET






4.69/5 (3 votes)
How to make a thin-as-possible .NET IEnumerable-wrapper around MFC list classes.
Introduction
When you design wrappers around unmanaged classes, it might come up that you need to wrap MFC collections, e.g.,
CStringList
s.
One way to (partially) do so is to wrap them as IEnumerable<T>
.
Background
CStringList
uses a POSITION
structure to iterate through the list. Basically, a "kinda-thin" wrapper layer
that can store this position internally
and provide the outside world with a nice IEnumerable
interface.
#pragma once
using namespace System;
using namespace System::Collections;
namespace MFCWrapper
{
public ref class CStringListWrapper : Generic::IEnumerable<String^>
{
public:
/// <summary>
// not a very nice syntax, but this is how
// explicit interface implementation is done in C++/CLI.
/// IEnumerable(of T).GetEnumerator()
/// </summary>
virtual Generic::IEnumerator<String^>^ GetEnumerator() sealed =
Generic::IEnumerable<String^>::GetEnumerator
{
return gcnew StringListEnumerator(this);
}
/// <summary>
// IEnumerable.GetEnumerator()
/// </summary>
virtual System::Collections::IEnumerator^ GetEnumeratorBase() sealed =
System::Collections::IEnumerable::GetEnumerator
{
return GetEnumerator();
}
/// <summary>
// Returns the pointer from the CStringList being wrapped
/// </summary>
CStringList * GetUnmanagedPointer()
{
return m_pStringList;
}
/// <summary>
// Provides the wrapper with a (new) pointer to a CStringList
/// </summary>
void SetUnmanagedPointer(CStringList * pList)
{
if(m_pStringList)// might replace old one
{
delete m_pStringList;
m_pStringList = NULL;
}
m_pStringList = pList;
}
/// <summary>
// Insert at the beginning
/// </summary>
void AddHead(String ^ str)
{
if(str == nullptr)
return;
m_pStringList->AddHead((CString) str);
}
/// <summary>
// Insert at the end
/// </summary>
//
void AddTail(String ^ str)
{
if(str == nullptr)
return;
m_pStringList->AddTail((CString) str);
}
/// <summary>
// remove the first occurence and returns true if an item has been removed
/// </summary>
bool Remove(String ^ str)
{
if(str == nullptr)
return false;
CString strNative = (CString) str;
POSITION pos = m_pStringList->Find(strNative);
if(pos)
{
m_pStringList->RemoveAt(pos);
return true;
}
return false;
}
/// <summary>
// Removes all occurences of str
/// </summary>
int RemoveAll(String ^ str)
{
if(str == nullptr)
return 0;
int numRemoved = 0;
CString strNative = (CString) str;
POSITION pos = m_pStringList->Find(strNative);
while(pos)
{
m_pStringList->RemoveAt(pos);
++numRemoved;
pos = m_pStringList->Find(strNative);
}
return numRemoved;
}
/// <summary>
// Clears list
/// </summary>
void Clear()
{
m_pStringList->RemoveAll();
}
/// <summary>
// Gets number of elements
/// </summary>
property int Count
{
int get()
{
return m_pStringList->GetCount();
}
}
CStringListWrapper(){ SetUnmanagedPointer(new CStringList()); }
internal:
CStringListWrapper(CStringList * pList){ SetUnmanagedPointer(pList); }
!CStringListWrapper(void) { if(m_pStringList){ delete m_pStringList; m_pStringList = NULL; } }
virtual ~CStringListWrapper(void){ this->!CStringListWrapper(); }
private:
ref struct StringListEnumerator : public Generic::IEnumerator<String^>
{
public:
StringListEnumerator(CStringListWrapper ^ wrapper)
{
// Initialize position
m_wrapper = wrapper;
m_pos = m_wrapper->GetUnmanagedPointer()->GetHeadPosition();
m_element = nullptr;
}
property String ^ Current
{
virtual String^ get()
{
return m_element;
}
};
property Object^ CurrentBase
{
virtual Object^ get() sealed = System::Collections::IEnumerator::Current::get
{
return Current;
}
};
virtual bool MoveNext()
{
// TODO: throw "Collection was modified; enumeration operation may not execute."
// if list had been modified while iterating
if(m_pos == NULL)
return false;
POSITION curPos = m_pos;
CString strCurrent = m_wrapper->GetUnmanagedPointer()->GetNext(curPos);
m_element = gcnew String (strCurrent);
m_pos = curPos;
return true;
}
virtual void Reset()
{
m_pos = m_wrapper->GetUnmanagedPointer()->GetHeadPosition();
}
virtual ~StringListEnumerator()
{
}
private:
String ^ m_element; // current element
POSITION m_pos; // current (unmanaged position) during iteration
CStringListWrapper ^ m_wrapper;
};
CStringList * m_pStringList; // CStringList being wrapped
};
}
Using the code
You can then iterate through the list, add/remove elements, etc., as this C# sample program illustrates:
using System.Diagnostics;
using MFCWrapper;
namespace MfcWrapperTest
{
class Program
{
static void Main(string[] args)
{
CStringListWrapper wrapper = new CStringListWrapper();
DumpList(wrapper);
wrapper.AddHead("two");
wrapper.AddTail("three");
wrapper.AddHead("one");
DumpList(wrapper); //one, two, three
wrapper.AddTail("three");
wrapper.AddTail("three");
wrapper.AddTail("three");
wrapper.RemoveAll("three");
wrapper.Remove("two");
DumpList(wrapper); // one
wrapper.RemoveAll("one");
wrapper.AddHead("five");
wrapper.AddTail("six");
wrapper.AddHead("four");
DumpList(wrapper); // four, five, six
wrapper.Clear();
DumpList(wrapper); // (empty)
}
static void DumpList(CStringListWrapper myStringListWrapper)
{
foreach (string s in myStringListWrapper)
Trace.WriteLine(s);
}
}
}
Points of Interest
Similar wrappers can be done with:
CPtrList
, especially if the type of the pointers inside the list is known, i.e., the pointers themselves can be wrapped. In this case you could wrap to anIEnumerable<T>
.CObList
. Again, if the runtime classes of the objects inside the List are known, the List can be wrapped asIEnumerable<T>
.CMapStringToString
,CMapStringToPtr
, etc.: The code can be adapted to wrapCMap
-classes toIDictionary<TKey, TValue>
.
For list classes, it might be suitable to instead wrap to IList
<T>
, which basically means additionally implementing
ICollection<T>
.