HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

A generic IEnumerator to enumerate COM collections

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
genericienumeratorcollectionscomenumerate

Problem

As I wrapped the VBIDE API, I encountered a number of "collection types" (Windows, CodePanes, VBComponents, VBProjects, References, etc.) - these types implement the non-generic IEnumerable interface, so if I wanted to be able to iterate my wrappers like this for example:

using (var projects = _vbe.VBProjects)
{
    foreach (var project in projects.Where(p => p.Protection == ProjectProtection.Unprotected))
    {
        yield return project.Name;
    }
}


...then I was going to be needing some custom IEnumerator implementation, because even if I could explicitly .Cast() every time, at runtime the cast would fail because the enumerator yields a COM object, not a wrapper type.

So I made a generic ComWrapperEnumerator, where TComCollection is the collection type (must implement IEnumerable) and TWrapperItem is the type of the SafeComWrapper to be created and returned - am I doing this right? I don't like the way I'm assuming the constructor only has a single T parameter (where T is the COM type being wrapped by TWrapperItem), is there a better way?

```
using System;
using System.Collections;
using System.Collections.Generic;

namespace Rubberduck.VBEditor.DisposableWrappers
{
public class ComWrapperEnumerator : IEnumerator
where TComCollection : IEnumerable
{
private readonly IEnumerator _internal;

public ComWrapperEnumerator(TComCollection items)
{
_internal = items.GetEnumerator();
}

public void Dispose()
{
var disposable = _internal as IDisposable;
if (disposable == null)
{
return;
}
disposable.Dispose();
}

public bool MoveNext()
{
return _internal.MoveNext();
}

public void Reset()
{
_internal.Reset();
}

public TWrapperItem Current
{
get { return (TWrapperItem)Act

Solution

Your ComWrapperEnumerator is generic in two types without needing to.
You don't really care about TComCollection aside that it must be an IEnumerable,
thus that generic type can disappear from your class and you will end up with a constructor method that receives an IEnumerable.

public ComWrapperEnumerator(IEnumerable items)
{
    _internal = items.GetEnumerator();
}


Normally the non generic version calls the generic version of the method, removing your code repetition:

IEnumerator IEnumerable.GetEnumerator()
{
    //call the generic GetEnumerator
    return GetEnumerator();
}


In this particular case I think there isn't any other way to improve the instantion of ComWrapperEnumerator.
However one way that normally is used to avoid specifying types on constructors is to specify a static method to instantiate the type.
If you look for example to Tuple.Create this is exactly what they do.

The hack could work with code like this:

public static ComWrapperEnumerator Create(T t, IEnumerable value){
    return new ComWrapperEnumerator(value);
}

//usage
ComWrapperEnumerator.Create(default(CodePane), ComObject);


You can always provide methods for all types that you care about:

public static ComWrapperEnumerator CreateCodePane(IEnumerable value){
    return new ComWrapperEnumerator(value);
}


But anyway, now that you have one less generic type maybe you are no longer concerned about that.


I don't like the way I'm assuming the constructor only has a single T
parameter (where T is the COM type being wrapped by TWrapperItem), is
there a better way?

Yes there is. Not only there is but would also remove the need of that pesky Activator.CreateInstace. The most straightforward approach that I can think of is to pass a Func that would receive the COM object and return an instance of the type you want. This would be a parameter of the constructor.

private readonly Func _createItem;
public ComWrapperEnumerator(IEnumerable items, Func createItem)
{
    _internal = items.GetEnumerator();
    _createItem = createItem;
}

public TWrapperItem Current
{
    get { return _createItem(_internal.Current); }
}

object IEnumerator.Current
{
    get { return Current; }
}


Obviously you can always fallback to Activator:

private TWrapperItem CreateItem(object value){
   return (TWrapperItem )Activator.CreateInstance(typeof(TWrapperItem), value);
}

public ComWrapperEnumerator(IEnumerable items, Func createItem = null)
{
    _internal = items.GetEnumerator();
    _createItem = createItem ?? CreateItem;
}


Note that if you go with this approach you may consider to come back to having two generic types on your class, so you could have a typesafe _createItem.

Code Snippets

public ComWrapperEnumerator(IEnumerable items)
{
    _internal = items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
    //call the generic GetEnumerator
    return GetEnumerator();
}
public static ComWrapperEnumerator<T> Create<T>(T t, IEnumerable value){
    return new ComWrapperEnumerator<T>(value);
}

//usage
ComWrapperEnumerator.Create(default(CodePane), ComObject);
public static ComWrapperEnumerator<CodePane> CreateCodePane(IEnumerable value){
    return new ComWrapperEnumerator<CodePane>(value);
}
private readonly Func<object, TWrapperItem> _createItem;
public ComWrapperEnumerator(IEnumerable items, Func<object, TWrapperItem> createItem)
{
    _internal = items.GetEnumerator();
    _createItem = createItem;
}

public TWrapperItem Current
{
    get { return _createItem(_internal.Current); }
}

object IEnumerator.Current
{
    get { return Current; }
}

Context

StackExchange Code Review Q#143165, answer score: 3

Revisions (0)

No revisions yet.