3

I wanted to create an extension method that would efficiently wrap single objects as IEnumerables. This was to avoid the cases where you end up putting a new [] {} in the middle of an expression. This is easy enough to do using the following method:

public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this TSource source)
{
    return new[] { source };
}

The problem is that this will be applied to any and all types (which is the expected behavior), but this will also have the side effect of making the method available on IEnumerable <T> instances. In the case where the resolved extended type is an IEnumerable<T>, I would simply like to return this IEnumerable, since the aternative is finding myself with a IEnumerable<IEnumerable<T>>, which is not really what you'd expect when calling the method.

Instinctively (and perhaps sleepily), I first created an overload that looked like this

public static IEnumerable<TSource> WrapAsEnumerable<TSource>(this IEnumerable<TSource> source)
{
    return source;
}

in order to handle the case where the type to wrap is an IEnumerable<T>, But of course, the control flow always resolves to the first method.

So, the question is: how could I create such a wrapping method that handles both the case where the extended parameter instance is an IEnumerable<T> and when it is not ?

Phil Gref
  • 987
  • 8
  • 19
  • The method call is not resolved to the first method. It uses [overloading resolution](https://msdn.microsoft.com/en-us/library/aa691336%28v=vs.71%29.aspx) at compile time. – Willem Van Onsem Jun 03 '15 at 20:05
  • What happens with non-generic collections i.e. `IEnumerable`? – Dustin Kingen Jun 03 '15 at 20:13
  • While they still can be iterated over, I would consider them as single instances that can and will be wrapped in an IEnumerable. – Phil Gref Jun 03 '15 at 20:18
  • I would say that the *expected behavior* calling "wrap into a sequence" on a sequence would create a sequence of sequences. There are plenty of situations where that'd be what you'd want to do. I'd say just leave what you have; it's not broken. – Servy Jun 03 '15 at 20:27
  • I completely agree that there are some cases where you would want this particular behavior, but I could always use a boolean parameter (with a default value) to decide what strategy to adopt. What I'm asking is: humor me for one second. Let's say the method was named GetEnumerableIfSingleElementOrIdentityIfElementIsEnumerable. I want to see if someone thinks of a way that I have missed to get the behavior I described. – Phil Gref Jun 03 '15 at 20:34

2 Answers2

3

Here is another attempt, inspired from Eric Lippert's excellent post at: https://stackoverflow.com/a/1451184/4955425.

You can control the overloading resolution by placing your 2 extension methods at different levels in 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'll always need to refer to both namespaces to get the right method invocation behavior.

using MyExtensions;
using MyExtensions.LowerPrecedenceNamespace;
Community
  • 1
  • 1
sstan
  • 35,425
  • 6
  • 48
  • 66
  • Why should it work? ***If*** the place where you want to call the extension, is inside an unrelated namespace, say `namespace MyApp.SomeTopic { ... }`, and ***if*** both `using` directives are placed at the same place (same namespace declaration "level"), both extension methods will be found at the same "search depth", and the original problem from the question is unsolved. I realize that something like this can work if my two "if" premises above do not hold, but it seems like an extremely dangerous approach! Behavior depends on current namespace and on placement of `using` directives. – Jeppe Stig Nielsen Jun 03 '15 at 22:29
1

Have you considered changing your signature a little bit, makes the code a little more complicated but makes the usage 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" });
    }
}
Ruskin
  • 1,504
  • 13
  • 25