bflemi3
bflemi3

Reputation: 6790

Dynamically casting to IEnumerable<T> or T

I have a class that accepts T and has a method that takes an HttpResponseMessage and modifies the content in the response.

public class MyClass<T> {

    HttpResponseMessage Modify(HttpResponseMessage response) {
        T content;
        if(response.TryGetContentValue(out content)) {
            DoSomethingWithContent(content);
        }
        return response;
    }

    public void DoSomethingWithContent(T content) {}

    public void DoSomethingWithContent(IEnumerable<T> content) {}
}

Used like this...

This works as long as the response content value is of type T, but sometimes it's IEnumerable<T>, and when that's the case TryGetContentValue() will return false since T is not IEnumerable<T>. So I created an overload for DoSomethingWithContent but I'm struggling to figure out an efficient way to dynamically cast or declare content to the proper type so the correct overload is called.

Answer

I went with recursives answer but I wanted to post the complete method for reference:

public class MyClass<T> {

    HttpResponseMessage Modify(HttpResponseMessage response) {
        object content;
        if(response.TryGetContentValue(out content)) {
            if(content is IEnumerable<T>)
                DoSomethingWithContent((IEnumerable<T>)content);
            else
                DoSomethingWithContent((T)content);
        }
        return response;
    }

    public void DoSomethingWithContent(T content) {}

    public void DoSomethingWithContent(IEnumerable<T> content) {}
}

Upvotes: 2

Views: 2225

Answers (5)

Mark Brackett
Mark Brackett

Reputation: 85625

Assuming DoSomething is the same thing for IEnumerable<T> and T (eg., that one delegates to the other) - I think it'd make more sense to just always use an IEnumerable<T> - after all, a T is just an IEnumerable<T> with 1 value.

Of course, that sidesteps the problem that TryGetContentValue will only ever give you back a T - not an IEnumerable<T>. You could handle that by first trying T and then fall back to IEnumerable<T>. I think pulling it out as an object and handling the cast yourself may work as well.

So - I think you'll end up with something like:

public class MyClass<T> {
    HttpResponseMessage Modify(HttpResponseMessage response) {
        IEnumerable<T> content = this.GetContent(response);
        DoSomethingWithContent(content);
        return response;
    }

    private IEnumerable<T> GetContent(HttpResponseMessage response) {
        object content;
        if (!response.TryGetContentValue(out content)) return new T[] { };
        if (content is T) return new T[] { (T)content };
        return content as IEnumerable<T> ?? new T[] { };
    }

    public void DoSomethingWithContent(IEnumerable<T> content) {
        foreach (var t in content) {
          // blah, blah
        }
    }
}

If DoSomethingWithContent(T) and DoSomethingWithContent(IEnumerable<T>) are in fact different - then you'd basically follow the same pattern as GetContent to make the branching decision.

Upvotes: 1

YK1
YK1

Reputation: 7602

This work out?

HttpResponseMessage Modify(HttpResponseMessage response) {
        T content;
        if(response.TryGetContentValue<T>(out content)) {
            DoSomethingWithContent(content);
        }
        else 
        {
           IEnumerable<T> content;
           if(response.TryGetContentValue<IEnumerable<T>>(out content)) {
            DoSomethingWithContent(content);
           }
        }
        return response;
    }

Upvotes: 1

Branko Dimitrijevic
Branko Dimitrijevic

Reputation: 52107

The IEnumerable<out T> is covariant, so if you just...

var enumerable = content as IEnumerable<T>;
if (enumerable == null)
    DoSomethingWithContent(content);
else
    DoSomethingWithContent(enumerable);

...it will still call DoSomethingWithContent(IEnumerable<T> content) even if the actual run-time type of content is IEnumerable<C>, where C inherits T.


This doesn't address why you are trying "fit" IEnumerable<T> in T in the first place?

Upvotes: 2

evanmcdonnal
evanmcdonnal

Reputation: 48076

Yeah there is circularity problem with your design. If MyClass<T> is initialized as for example MyClass<List<string>> then T = List<string> so the overloaded definition won't actually get invoked. What you actually need to do is use only a single method but have a clause to test if T implements IEnumerable<T>. You can do this like;

if (content is IEnumerable<T>)

I'm not entirely sure that will work though because you may even have to specify the type the IEnumberable uses. If that's the case then check if it implements the non-generic IEnumberable because all collections that implement the generic version also implement the non-generic version.

Upvotes: 1

recursive
recursive

Reputation: 86064

You can use is to determine which case to use.

if (content is IEnumerable<T>)
    DoSomethingWithContent((IEnumerable<T>)content);
else
    DoSomethingWithContent(content);

If you're feeling sassy, you could just use the runtime binder by casting to dynamic, but that's not recommended.

DoSomethingWithContent((dynamic)content);

Upvotes: 7

Related Questions