2009-04-23 11 views
3

Ich bin ein bisschen rostig auf Generika, versuchen die folgenden zu tun, aber der Compiler beschwert sich:C# Generics Frage

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 wie Sie sehen können, i Inhalt einer Tabelle in ein Objekt auszugeben versuche (Übergeben einer DataRow an den Konstruktor) und fügen Sie dann das Objekt zur Auflistung hinzu. Es beschwert sich, dass T kein Typ oder Namespace ist, über den es informiert ist und den ich nicht verwenden kann, wenn es sich um eine nicht generische Deklaration handelt.

Ist das nicht möglich?

Antwort

21

Es gibt zwei große Probleme:

  • Sie können keine Konstruktoreinschränkung angeben, die einen Parameter
  • Ihre Methode ist nicht generisch nimmt - es PopulateCollection<T> statt PopulateCollection sein sollte.

Sie haben bereits eine Einschränkung, dass T : BusinessBase, so um das erste Problem zu bekommen empfehle ich Ihnen eine Zusammenfassung hinzufügen (oder virtuelle) Methode in BusinessBase:

public abstract void PopulateFrom(DataRow dr); 

auch einen parameter Konstruktoreinschränkung hinzufügen zu T.

Ihre Methode kann dann worden:

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

Alternativ könnten Sie:

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

Wenn Sie .NET 3.5 verwenden sind, werden Sie diese etwas einfacher mit der Erweiterungsmethode in DataTableExtensions machen machen Sie es selbst zu einer Erweiterungsmethode (wieder unter der Annahme von .NET 3.5) und übergeben Sie eine Funktion zum Zurückgeben von Instanzen:

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

Ihre Anrufer würde dann schreiben:

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

Dies setzt voraus, Sie gehen einen Konstruktor mit einem DataRow nehmen. Dies hat den Vorteil, dass Sie unveränderliche Klassen schreiben können (und solche, die keinen parameterlosen Konstruktor haben), aber das bedeutet, dass Sie nicht generisch arbeiten können, ohne auch die "Factory" -Funktion zu haben.

+0

+1 klar auf die Probleme, und für die letzte Version. Ich denke nicht, dass die Zwischenversion in diesem Fall viel einfacher ist als die Foreach. – eglasius

+0

Ich habe nicht die Macht zu bearbeiten, so dass jeder, der kann, ändern Rückgabe dt.Rows.AsEnumerable(). Wählen Sie (Selektor) .ToList(); bis Rückgabe dt.AsEnumerable(). Wählen Sie (Selektor) .ToList(); seit AsEnumerable ist eine Erweiterung Methode für die DataTable nicht in der .Rows-Auflistung. – AngryHacker

+0

@AngryHacker: Danke, fertig. –

2

Sie wahrscheinlich die new generische Einschränkung auf T hinzufügen müssen, wie folgt:

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

ich keine DataRow in den Konstruktor übergeben können, aber Sie können das lösen, indem es auf eine Eigenschaft von BusinessBase zuweisen

0
where T: BusinessBase 

sollte haben Einschränkung neuer() ich denke,

hinzugefügt
3

Die einzige constraint Sie können angeben, welche ermöglicht die Erstellung neuer Instanzen ist new() - im Grunde ein parameterloser Konstruktor.Um dies zu umgehen, entweder tun:

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

Oder

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

Ein möglicher Weg ist:

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

Öffentliche Klasse BusinessBase {public DataRow DataRow {get; einstellen; }} – Simon

0

Es ist möglich. Ich habe genau das gleiche in meinem Rahmen. Ich hatte genau das selbe Problem wie du und so habe ich es gelöst. Relevante Snippets aus dem Framework veröffentlichen. Wenn ich mich an die Korrektheit erinnere, war das größte Problem die Anforderung, einen parameterlosen Konstruktor aufzurufen.

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 diesem Szenario hat die Basisklasse statische Methode Objekte seiner abgeleiteten Klassen, die TableRow nimmt mit Konstruktor zu instanziiert.