Sunday, May 1, 2011

C# Generics question

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?

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

  • where T: BusinessBase
    

    Should have have restriction of new() I think added

  • 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.

    eglasius : +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.
    AngryHacker : 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.
    Jon Skeet : @AngryHacker: Thanks, done.
  • 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;
    }
    
  • 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;
        }
    
    Simon : public class BusinessBase{ public DataRow DataRow { get; set; }}
  • 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.

0 comments:

Post a Comment

Note: Only a member of this blog may post a comment.