Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

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

4.69/5 (3 votes)
5 Sep 2012CPOL 14.9K  
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., CStringLists. 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.

MC++
#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:

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);
        }
    }
}

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 an IEnumerable<T>.
  • CObList. Again, if the runtime classes of the objects inside the List are known, the List can be wrapped as IEnumerable<T>.
  • CMapStringToString, CMapStringToPtr, etc.: The code can be adapted to wrap CMap-classes to IDictionary<TKey, TValue>.

For list classes, it might be suitable to instead wrap to IList<T>, which basically means additionally implementing ICollection<T>.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)