tags:

views:

93

answers:

3

Hi,

Please consider the following snippet from an implementation of the Interpreter pattern:

public override object Execute(Interpreter interpreter, object ctx) {
    var list = ctx as IEnumerable<string>;
    return (list != null) ? list.FirstOrDefault() : null;
}

What about if I want to use the same function for integers?

public override object Execute(Interpreter interpreter, object ctx) {
    var list = ctx as IEnumerable<string>;
    if (list != null) return list.FirstOrDefault();

    var list = ctx as IEnumerable<int>;
    return (list != null) ? list.FirstOrDefault() : null;
}

What I wanted was something like:

public override object Execute(Interpreter interpreter, object ctx) {
    var list = ctx as IEnumerable;
    return (list != null) ? list.FirstOrDefault() : null;
}

But Linq doesn't act on IEnumerables. Instead, to get to this solution, I would be forced to write something like:

public override object Execute(Interpreter interpreter, object ctx) {
    var list = ctx as IEnumerable;
    if (list != null) foreach(var i in list) { yield return i; return; }
    return null;
}

Or use a generic method:

public override T Execute<T>(Interpreter interpreter, object ctx) {
    var list = ctx as IEnumerable<T>;
    return (list != null) ? list.FirstOrDefault() : null;
}

Which would break the Interpreter pattern (as it was implemented in this system). Covariance would also fail (at least in C#3), though would it work, it would be the exact behavior I wanted:

public override object Execute(Interpreter interpreter, object ctx) {
    var list = ctx as IEnumerable<object>;
    return (list != null) ? list.FirstOrDefault() : null;
}

So, my question is: what's the best way to achieve the intended behavior?


Addendum: digEmAll suggested something like the following code:

var list = ctx as IEnumerable;
return (list != null) ? list.Cast<object>().FirstOrDefault() : null;

This is a good answer for stuff like FirstOrDefault(). The problem comes with things like Reverse:

var list = ctx as IEnumerable;
return (list != null) ? list.Cast<object>().Reverse() : null;

I could fed this method a List<int> but I would get back a List<object>. No way to get around covariance, I know. So I guess digEmAll code is the best answer so far.


Thanks :-)

+3  A: 

Something like this, could fit your needs ?

public override object Execute(Interpreter interpreter, object ctx) {
    var nonGenericlist = ctx as IEnumerable;
    if (nonGenericlist == null)
       return null;
    var list = nonGenericlist.Cast<object>();
    return (list != null) ? list.FirstOrDefault() : null;
}
digEmAll
Actually, it will. In the meantime, we've come up with exactly your solution. I don't know the performance impact on Casting every element of the collection, but I'll accept your answer unless a better one comes up.
Hugo S Ferreira
Cast<>() uses deffered execution so will only cast when needed, in this case you would get at most 1 cast.
jk
A: 

Don't worry too much about breaking design patterns if the code does what you want. Design patterns are tools that are solutions to particular problems. If you can't fit your solution into a pattern then the pattern is not the correct solution. If every tool in your toolbox is some kind of hammer, every solution boils down to "hit it with a hammer". Sometimes the correct soltuion is to use a scredriver or even invent a new tool.

Ben Robinson
I know ;-) It's not the problem of "breaking the pattern" as it was specified; more that the whole system is dependent on acting upon 'objects' and I don't want to break this specific implementation.
Hugo S Ferreira
+2  A: 

It seems that you want to have typesafety and LINQ support while simultaneously not defining the types involved - which you can't do in C#.

One option is providing overloads of Execute that rather than taking object ctx can take the appropriate IEnumerable<T> as a parameter?

For instance, you could have several overloads:

public override object Execute(Interpreter interpreter, IEnumerable<int> ctx)
public override object Execute(Interpreter interpreter, IEnumerable<string> ctx)
public override object Execute(Interpreter interpreter, IEnumerable<...> ctx)

Alternatively, you could re-eimplement the LINQ methods you care about to work with plain IEnumerable. Doing so for FirstOrDefault is relatively easy:

public static object FirstOrDefault( this IEnumerable sequence )
{
    if( sequence == null ) return null;
    using( var iter = sequence.GetEnumerator() )
    {
        if( iter.MoveNext() )
            return iter.Current;
        return null;
    }
}

Re-implementing all of LINQ against IEnumerable will not be fun ... or in some cases easy. Your best bet would be to create an adapter that allows you to treat IEnumerable as IEnumerable<object> - then you can use it regardless of whether you're dealing with sequences of value types or reference types:

public static IEnumerable<object> AsObjectSequence( this IEnumerable sequence )
{
    foreach( object x in sequence )
        yield return x;
}

EDIT: It turns out, this is in principle, equivalent to using:

sequence.Cast<object>()

so yoy may want to use that, since it's more concise and directly built into LINQ.

LBushkin