patterncsharpMinor
A generic IEnumerator to enumerate COM collections
Viewed 0 times
genericienumeratorcollectionscomenumerate
Problem
As I wrapped the VBIDE API, I encountered a number of "collection types" (
...then I was going to be needing some custom
So I made a generic
```
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
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
You don't really care about
thus that generic type can disappear from your class and you will end up with a constructor method that receives an
Normally the non generic version calls the generic version of the method, removing your code repetition:
In this particular case I think there isn't any other way to improve the instantion of
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
The hack could work with code like this:
You can always provide methods for all types that you care about:
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
Obviously you can always fallback to
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
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.