views:

403

answers:

2

I have the following extension method, and would like to make it more generic so I don't have to implement it for every class in our domain.

public static IList<User> ToList(this DataTable table)
{
    IList<User> users = new List<User>();

    foreach (DataRow row in table.Rows)
        users.Add(User.FromDataRow(row));

    return users;
}

Is there any way to work around this frustrating limitation?

edit: the below paragraph is bollocks, but I'm keeping it so one of the answers makes sense to future readers:

User, as well as my other classes, implements IDataModel. IDataModel only requires 1 method, FromDataRow(DataRow row). Putting a where into the function prototype obviously doesn't help.

+9  A: 

When you only need one method, think Func... perhaps a Func<DataRow, T>

public static IList<T> ToList<T>(this DataTable table,
      Func<DataRow,T> converter)
{
    IList<T> list = new List<T>();

    foreach (DataRow row in table.Rows)
        list.Add(converter(row));

    return list;
}

Then call table.ToList<User>(User.FromDataRow)

Marc Gravell
you can remove the first parameter of ToList in the example call.
JaredPar
It should be able to infer type T from the Func.
Samuel
@Samuel, I believe method groups do not participate in type inference.
JaredPar
Kind of sucks that I have to pass a function, but at least it works! Thanks a ton for this :)
Stuart Branham
@JaredPar: I was wrong, it cannot infer the type.
Samuel
This is a good answer to the question, but Stuart's design allows calling ToList<User> on a DataTable that contains rows that can't be converted to User. Compiler won't warn again this. ToList will then fail or return IList full of nulls.
Petar Repac
Well, it's like a typecast i guess. Can it be improved ?
Petar Repac
@Petar - I'm not sure I understand the question; you could skip nulls returned from `converter` - but there is no fundamental difference ("re Stuart's design allows...") other than one uses a function...
Marc Gravell
It was mainly an observation in the sense that there is an "explicit cast" (or conversion) hidden in the code that calls ToList<T>. The user of the function must take care that the conversion is possible so that he doesn't try to build List<User> from AccountsDataTable.
Petar Repac
If this were an open-source API or something, I would heartily agree that it's a bad idea. Thankfully, this code won't be floating off into space anytime soon. Most of the time, we want our multi-row data in the form of a DataTable. Sometimes though, an IList<T> works better. This extension method is an easy, convenient way to achieve that.
Stuart Branham
+2  A: 

In your example code, you're using a static method to create the user from the DataRow:

foreach (DataRow row in table.Rows)
    users.Add(User.FromDataRow(row));

But, you can't use static methods to implement an interface.

Assuming that your interface looks like this:

public interface IDataModel {
    void FromDataRow(DataRow row);
}

then your User class will have an instance method FromDataRow(), not a static one.

If your classes have parameterless constructors, then you could write this:

public static IList<T> ToList<T>(this DataTable table)
    where T : IDataModel, new()
{
    IList<T> results = new List<T>();

    foreach (DataRow row in table.Rows)
    {
        T item = new T();
        item.FromDataRow(row);
        results.Add(item);
    }

    return users;
}

The IDataModel constraint on <T> requires the type to implement IDataModel.
The new() constraint on <T> requires the type to have a parameterless constructor.

Bevan
Ah yes, I wasn't thinking straight on that interface. Thanks for pointing that mistake out. This solution looks pretty swell. I'll have to do some thinking on which route to go. FromDataRow is actually a static method.
Stuart Branham