Creating an IEnumerables Type Wrap Extension Method
I wanted to create an extension method that would effectively wrap individual objects as IEnumerables. This was to avoid cases where you insert new [] {}
in the middle of an expression. It's easy enough to do it using the following method:
public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this TSource source)
{
return new[] { source };
}
The problem is that this will apply to all types (which is the expected behavior), but it will also have the side effect of using the method on instances IEnumerable <T>
. In case the allowed extended type is IEnumerable<T>
, I would just like to return this IEnumerable, since the alternate lookup is in IEnumerable<IEnumerable<T>>
, which is not actually what you expected when the method was called.
Instinctively (and possibly sleepily), I first created an overload that looked like this:
public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this IEnumerable<TSource> source)
{
return source;
}
to handle the case where the wrap type is IEnumerable<T>
, but of course control flow always allows the first method.
So the question is, how could I create a wrapper method like this that handles as a case when an extended parameter instance is IEnumerable<T>
and when it isn't?
source to share
Here's another attempt, inspired by Eric Lippert a great message: fooobar.com/questions/107301 / ... .
You can control overload resolution by placing your 2 extension methods at different levels of the namespace hierarchy.
namespace MyExtensions
{
public static class HighPrecendenceExtensions
{
public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this IEnumerable<TSource> source)
{
return source;
}
}
namespace LowerPrecedenceNamespace
{
public static class LowPrecedenceExtensions
{
public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this TSource source)
{
return new[] { source };
}
}
}
}
However, the downside is that you always need to refer to both namespaces to get the correct method invocation behavior.
using MyExtensions;
using MyExtensions.LowerPrecedenceNamespace;
source to share
Do you think you've changed your signature a bit makes the code a little more complicated, but makes it extremely simple.
public static class Extensions
{
public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this object source)
{
var allInterfaces = source.GetType().GetInterfaces();
IEnumerable<Type> allEnumerableInterfaces = allInterfaces.Where(t => t.Name.StartsWith("IEnumerable"));
if (!allEnumerableInterfaces.Any())
return new[] { (TSource)source };
IEnumerable<Type> genericEnumerableOfTSource = allEnumerableInterfaces.Where(t => t.GenericTypeArguments.Contains(typeof(TSource)));
// we have a generic implementation
if (genericEnumerableOfTSource.Any())
{
return (IEnumerable<TSource>) source;
}
return new[] { (TSource)source }; ;
}
}
[TestFixture]
public class Tests
{
[Test]
public void should_return_string_an_enumerable()
{
const string aString = "Hello World";
var wrapped = aString.WrapAsEnumerable<string>();
wrapped.ShouldAllBeEquivalentTo(new[] {"Hello World"});
}
[Test]
public void should_return_generic_enumerable_unwrapped()
{
var aStringList = new List<string> { "Hello", "World" };
var wrapped = aStringList.WrapAsEnumerable<string>();
wrapped.ShouldAllBeEquivalentTo(new[] { "Hello", "World" });
}
[Test]
public void should_return_non_generic_enumerable_unwrapped()
{
var aStringArray = new[] {"Hello", "World"};
var wrapped = aStringArray.WrapAsEnumerable<string>();
wrapped.ShouldAllBeEquivalentTo(new[] { "Hello", "World" });
}
}
source to share