2009-04-23 10 views
3

Je suis un peu rouillé sur les médicaments génériques, en essayant de faire ce qui suit, mais le compilateur se plaint:C# Generics question

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; 
} 

Comme vous pouvez le voir, je suis en train de jeter le contenu d'une table dans un objet (en passant un DataRow au constructeur), puis ajoutez l'objet à la collection. il se plaint que T n'est pas un type ou un espace de noms qu'il connaît et que je ne peux pas utiliser où sur une déclaration non générique.

N'est-ce pas possible?

Répondre

21

Il y a deux grands problèmes:

  • Vous ne pouvez pas spécifier une contrainte de constructeur qui prend un paramètre
  • Votre méthode est pas générique - il devrait être PopulateCollection<T> au lieu de PopulateCollection.

Vous avez déjà une contrainte qui T : BusinessBase, afin de contourner le premier problème, je vous suggère d'ajouter un résumé (ou virtuel) méthode BusinessBase:

public abstract void PopulateFrom(DataRow dr); 

ajouter également une contrainte constructeur parameterless à T.

Votre méthode peut alors devenir:

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; 
} 

Si vous utilisez .NET 3.5, vous pouvez faire cela en utilisant un peu plus simple la méthode d'extension dans 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(); 
} 

Sinon, vous pourriez en faire une méthode d'extension elle-même (encore une fois, en supposant .NET 3.5) et transmettre une fonction pour retourner les instances:

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

Vos correspondants seraient alors écrire:

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

Cela suppose que vous retournerez à avoir un constructeur de prendre un DataRow. Cela a l'avantage de vous permettre d'écrire des classes immuables (et celles qui n'ont pas de constructeur sans paramètre) mais cela signifie que vous ne pouvez pas travailler de façon générique sans avoir la fonction "factory".

+0

+1 effacer sur les questions, et pour la dernière version. Je ne pense pas que la version intermédiaire soit beaucoup plus simple que le foreach dans ce cas. – eglasius

+0

Je n'ai pas le pouvoir d'éditer, donc quiconque peut, changer retourner dt.Rows.AsEnumerable(). Sélectionner (sélecteur) .ToList(); à return dt.AsEnumerable(). Sélectionnez (sélecteur) .ToList(); depuis AsEnumerable est une méthode d'extension sur le DataTable pas sur la collection .Rows. – AngryHacker

+0

@AngryHacker: Merci, c'est fait. –

2

Vous devez probablement ajouter la new contrainte générique sur T, comme suit:

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

Je ne peux pas passer un DataRow dans le constructeur, mais vous pouvez résoudre ce en attribuant à une propriété de BusinessBase

0
where T: BusinessBase 

Il devrait y avoir de nouvelles restrictions ont() Je pense ajouté

3

Le seul constraint que vous pouvez spécifier et qui permet la création de nouvelles instances est new() - en principe, un constructeur sans paramètre.Pour contourner ce faire soit:

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; 
} 

Ou

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; 
} 
2

Une voie possible est:

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; 
    } 
+0

public class BusinessBase {public DataRow DataRow {get; ensemble; }} – Simon

0

Il est possible. J'ai exactement la même chose dans mon cadre. J'ai eu exactement le même problème que vous et voici comment je l'ai résolu. Publication d'extraits pertinents du cadre Si je me souviens bien, le plus gros problème était l'exigence d'appeler un constructeur sans paramètre.

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; 
     } 

Dans ce scénario, la classe de base a une méthode statique pour instancier des objets de ses classes dérivées à l'aide de constructeur qui accepte tablerow.