views:

531

answers:

6

I am a bit rusty on generics, trying to do the following, but the compiler complains:

protected List<T> PopulateCollection(DataTable dt) where T: BusinessBase
{
    List<T> lst = new List<T>();
    foreach (DataRow dr in dt.Rows)
    {
        T t = new T(dr);
        lst.Add(t);
    }
    return lst;
}

So as you can see, i am trying to dump contents of a Table into an object (via passing a DataRow to the constructor) and then add the object to collection. it complains that T is not a type or namespace it knows about and that I can't use where on a non-generic declaration.

Is this not possible?

+2  A: 

You probably need to add the new generic constraint on T, as follows:

protected List<T> PopulateCollection<T>(DataTable dt) where T : BusinessBase, new()
...

I can't pass a DataRow into the constructor, but you can solve that by assigning it to a property of BusinessBase

JacobE
A: 
where T: BusinessBase

Should have have restriction of new() I think added

Preet Sangha
+20  A: 

There are two big problems:

  • You can't specify a constructor constraint which takes a parameter
  • Your method isn't currently generic - it should be PopulateCollection<T> instead of PopulateCollection.

You've already got a constraint that T : BusinessBase, so to get round the first problem I suggest you add an abstract (or virtual) method in BusinessBase:

public abstract void PopulateFrom(DataRow dr);

Also add a parameterless constructor constraint to T.

Your method can then become:

protected List<T> PopulateCollection(DataTable dt)
    where T: BusinessBase, new()
{
    List<T> lst = new List<T>();
    foreach (DataRow dr in dt.Rows)
    {
        T t = new T();
        t.PopulateFrom(dr);
        lst.Add(t);
    }
    return lst;
}

If you're using .NET 3.5, you can make this slightly simpler using the extension method in DataTableExtensions:

protected List<T> PopulateCollection<T>(DataTable dt)
    where T: BusinessBase, new()
{
    return dt.AsEnumerable().Select(dr => 
    { 
        T t = new T();
        t.PopulateFrom(dr);
    }.ToList();
}

Alternatively, you could make it an extension method itself (again, assuming .NET 3.5) and pass in a function to return instances:

static List<T> ToList<T>(this DataTable dt, Func<DataRow dr, T> selector)
    where T: BusinessBase
{
    return dt.AsEnumerable().Select(selector).ToList();
}

Your callers would then write:

table.ToList(row => new Whatever(row));

This assumes you go back to having a constructor taking a DataRow. This has the benefit of allowing you to write immutable classes (and ones which don't have a parameterless constructor) it but does mean you can't work generically without also having the "factory" function.

Jon Skeet
+1 clear on the issues, and for the last version. I don't think the intermediate version is much simpler than the foreach in this case.
eglasius
I don't have the power to edit, so anyone who can, change return dt.Rows.AsEnumerable().Select(selector).ToList();to return dt.AsEnumerable().Select(selector).ToList();since AsEnumerable is an extention method on the DataTable not on the .Rows collection.
AngryHacker
@AngryHacker: Thanks, done.
Jon Skeet
+3  A: 

The only constraint you can specify which allows for creation of new instances is new() - basically, a parameterless constructor. To circumvent this do either:

interface ISupportInitializeFromDataRow
{
    void InitializeFromDataRow(DataRow dataRow);
}

protected List<T> PopulateCollection<T>(DataTable dt) 
    where T : BusinessBase, ISupportInitializeFromDataRow, new()
{
    List<T> lst = new List<T>();
    foreach (DataRow dr in dt.Rows)
    {
        T t = new T();
        t.InitializeFromDataRow(dr);

        lst.Add(t);
    }
    return lst;
}

Or

protected List<T> PopulateCollection<T>(DataTable dt, Func<DataRow, T> builder) 
    where T : BusinessBase
{
    List<T> lst = new List<T>();
    foreach (DataRow dr in dt.Rows)
    {
        T t = builder(dr);        
        lst.Add(t);
    }
    return lst;
}
Anton Gogolev
+2  A: 

A possible way is:

protected List<T> PopulateCollection<T>(DataTable dt) where T: BusinessBase, new()
    {
        List<T> lst = new List<T>();
        foreach (DataRow dr in dt.Rows)
        {
            T t = new T();
            t.DataRow = dr;
            lst.Add(t);
        }
        return lst;
    }
robi
public class BusinessBase{ public DataRow DataRow { get; set; }}
Simon
A: 

It is possible. I have exactly the same thing in my framework. I had exactly the same problem as you and this is how I solved it. Posting relevant snippets from the framework. If I remember correclty, the biggest problem was requirement to call parameterless constructor.

 public class Book<APClass> : Book where APClass : APBase
        private DataTable Table ; // data
        public override IEnumerator GetEnumerator()
        {                        
            for (position = 0;  position < Table.Rows.Count;  position++)           
                 yield return APBase.NewFromRow<APClass>(Table.Rows[position], this.IsOffline);
        }
   ...


  public class APBase ...
  {
    ...
    internal static T NewFromRow<T>(DataRow dr, bool offline) where T : APBase
        {

            Type t = typeof(T);
            ConstructorInfo ci;

            if (!ciDict.ContainsKey(t))
            {
                ci = t.GetConstructor(new Type[1] { typeof(DataRow) });
                ciDict.Add(t, ci);
            }
            else ci = ciDict[t];

            T result = (T)ci.Invoke(new Object[] { dr });

            if (offline)
                result.drCache = dr;    

            return result;
        }

In this scenario, base class has static method to instantiate objects of its derived classes using constructor that accepts tablerow.

majkinetor