views:

276

answers:

7

I have a custom class called Rows that implements IEnumerable<Row>. I often use LINQ queries on Rows instances:

Rows rows = new Rows { row1, row2, row3 };
IEnumerable<Row> particularRows = rows.Where<Row>(row => condition);

What I would like is to be able to do the following:

Rows rows = new Rows { row1, row2, row3 };
Rows particularRows = (Rows)rows.Where<Row>(row => condition);

However, I get a "System.InvalidCastException: Unable to cast object of type 'WhereEnumerableIterator1[NS.Row]' to type 'NS.Rows'". I do have a Rows constructor taking IEnumerable<Row>, so I could do:

Rows rows = new Rows { row1, row2, row3 };
Rows particularRows = new Rows(rows.Where<Row>(row => condition));

This seems bulky, however, and I would love to be able to cast an IEnumerable<Row> to be a Rows since Rows implements IEnumerable<Row>. Any ideas?

A: 

Does this work?

Rows particularRows = (Rows)(rows.Where<Row>(row => condition).ConvertAll(r=>(Row)r));

You could also add an extension method wrapper for the above (which would be the cleanest solution) so you could do something like:

Rows particularRows = rows.Where<Row>(row => condition).ToRows();
jvenema
"Cannot convert type 'System.Collections.Generic.List<NS.Row>' to 'NS.Rows'"
Sarah Vessels
+3  A: 

extension method to the rescue.

public static Rows ToRows(this IEnumerable<Row> source)
{
  return new Rows(source);
}

Rows particularRows = rows.Where(...).ToRows();


"Casting" is not a good fit for the problem. Every Rows is an IEnumerable<Row>, but not every IEnumerable<Row> is a Rows

David B
This is probably better than what I was thinking I'd go with, which was to override `Where`, etc. in `Rows` to return `Rows` instead of `IEnumerable<Row>`.
Sarah Vessels
+1  A: 

While Rows may implement IEnumerable<Row> that doesn't mean that any IEnumerable<Row> is a Rows, so the compiler can't just let you make that assumption.

Eric Petroelje
+1  A: 

You have to remember that, while you can cast up the inheritence chain (Row -> IEnumerable), there's no reliable way to cast down the inheritence chain.

Consider the following:

List<Row> rows = new List<Row>();

// Fill the list
IEnumerable<Row> rowsAsEnumerable = (IEnumerable<Row>)rows;

Rows realRows = (Rows)rowsAsEnumerable;

Since rowsAsEnumerable was never a Rows object, how would the system handle the cast?

The answer is that it couldn't because even though a Rows object is always an IEnumerable, an IEnumerable isn't always a Rows object.

Update

Since I don't like to be a dream crusher, I'll re-itterate what others have said.

You can use a simple Extension method to make things a little cleaner from your example:

public static Rows ToRows(this IEnumerable<Row> rowsAsEnum)
{
    return new Rows(rowsAsEnum);
}

Now you can change your call to:

Rows particularRows = rows.Where<Row>(row => condition).ToRows();
Justin Niessner
I thought I was hitting this wall, but I had hope! You're crushing my casting hopes right and left here... :(
Sarah Vessels
@Sarah: Don't worry; it is possible. You need to write a `ToRows()` extension method.
SLaks
@Sarah - I hate to hear I'm crushing hopes. While you can't directly cast, you can use one of the other work-arounds here (including defining Explicit and Implicit casts in your Rows class).
Justin Niessner
+2  A: 

You can only cast an object to a particular class if the object is actually an instance of that class.

In your case, the IEnumerable<Row> that you get from the LINQ call isn't a Rows instance, and the .Net runtime cannot magically convert it to a Rows instance.

Instead, you need to make a function that creates a new Rows instance from an IEnumerable<Row>. The function should probably be either a contructor overload or an extension method.

Note, by the way, that standard .Net naming conventions indicate that your Rows class should actually be called RowCollection.

SLaks
Is there a site somewhere that describes the different meanings intended by Collection v. List v. Enumerable?
Sarah Vessels
`IList`, `ICollection`, and `IEnumerable` are three different interfaces; you can see the differences on MSDN. Custom collection classes should always end in `Collection`.
SLaks
Should my class really be called `RowCollection` if it does not implement `ICollection`?
Sarah Vessels
Yes, it should. It should probably (but not necessarily) implement `IList<Row>`, too.
SLaks
Now that it does. :)
Sarah Vessels
`IList<T>` already implements `ICollection<T>`.
SLaks
A: 

You need to define an explicit cast in your class:

public class Rows : IEnumerable<Row>
{
    public static explicit operator MyType(IEnumerable<Row> x)
    {
        return new Rows(x); // using your constructor
    }
}

This will tell C# to allow your cast:

Rows particularRows = (Rows)rows.Where<Row>(row => condition);
Keltex
This looks promising, but I got "'NS.Rows.explicit operator NS.Rows(System.Collections.Generic.IEnumerable<NS.Row>)': user-defined conversions to or from an interface are not allowed".
Sarah Vessels
This will not work _at all_.
SLaks
I modified the conversion... but you guys are wrong. This will work.
Keltex
@David - You need to read up on explicit and how it works. What explicit does is say "It's OK to cast from IEnumerable<Row> to Rows and here's the implementation". Which will get rid of the error message.
Keltex
A: 

Basically whatever solution you come up with here is going to involve wrapping around that constructor which takes an IEnumerable

My original answer defined the casting solution (but I failed to compile and see the error).

What I would do is to define an extension method for IEnumerable:

public static Rows ToRows(this IEnumerable<Row> rows)
{
  return new Rows(rows);
}

Then you can use it quite cleanly:

public void Foo(IEnumerable<Row> rows)
{
   Rows r = rows.Where(r => condition).ToRows();
}
Andras Zoltan