为 .NET 包装 MFC 的列表类为 IEnumerable<T>






4.69/5 (3投票s)
如何为 MFC 列表类创建一个尽可能精简的 .NET IEnumerable 包装器。
引言
在为非托管类创建包装器时,你可能会需要包装 MFC 集合,例如 CStringList
。 一种方法(部分地)就是将它们包装为 IEnumerable<T>
。
背景
CStringList
使用 POSITION
结构来遍历列表。 基本上,一个“相当精简”的包装层,可以内部存储这个位置,并为外部世界提供一个友好的 IEnumerable
接口。
#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
};
}
使用代码
然后,你可以像这个 C# 示例程序演示的那样,遍历列表、添加/删除元素等。
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);
}
}
}
关注点
类似的包装也可以应用于
CPtrList
,特别是如果列表中指针的类型已知,即指针本身可以被包装。 在这种情况下,你可以包装为IEnumerable<T>
。CObList
。 同样,如果 List 内部对象的运行时类已知,则可以将 List 包装为IEnumerable<T>
。CMapStringToString
、CMapStringToPtr
等:代码可以调整为将CMap
类包装为IDictionary<TKey, TValue>
。
对于列表类,可以考虑将其包装为 IList
<T>
,这基本上意味着额外实现 ICollection<T>
。