Wednesday, April 20, 2011

How can I work around C#'s limitation on calling static functions on a Generic type

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.

From stackoverflow
  • 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)

    JaredPar : you can remove the first parameter of ToList in the example call.
    Samuel : It should be able to infer type T from the Func.
    JaredPar : @Samuel, I believe method groups do not participate in type inference.
    Stuart Branham : Kind of sucks that I have to pass a function, but at least it works! Thanks a ton for this :)
    Samuel : @JaredPar: I was wrong, it cannot infer the type.
    Petar Repac : This is a good answer to the question, but Stuart's design allows calling ToList 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 ?
    Marc Gravell : @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...
    Petar Repac : It was mainly an observation in the sense that there is an "explicit cast" (or conversion) hidden in the code that calls ToList. The user of the function must take care that the conversion is possible so that he doesn't try to build List from AccountsDataTable.
    Stuart Branham : 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 works better. This extension method is an easy, convenient way to achieve that.
  • 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.

    Stuart Branham : 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.

0 comments:

Post a Comment