2010-02-01 15 views
6

Ich habe eine Anzahl von Klassen, die Tabellen in einer Datenbank widerspiegeln. Ich möchte eine Basisklasse haben, die einige grundlegende Funktionalität hat (sagen wir, es hätte ein "isDirty" -Flag) und ein statisches Array von Strings mit den Spaltennamen, wie sie in der Datenbank erscheinen. Der folgende Code funktioniert nicht, sondern zeigt, was ich tun möchte:Statische Membervererbung in C#

public class BaseRecord { 
    public bool isDirty; 
    public object [] itemArray; 
    public static string [] columnNames;  
} 

public class PeopleRec : BaseRecord { 
} 

public class OrderRec : BaseRecord { 
} 

public static void Main() { 
    PeopleRec.columnNames = new string[2]; 
    PeopleRec.columnNames[0]="FIRST_NAME"; 
    PeopleRec.columnNames[1]="LAST_NAME"; 

    OrderRec.columnNames = new string[4]; 
    OrderRec.columnNames[0] = "ORDER_ID"; 
    OrderRec.columnNames[1] = "LINE"; 
    OrderRec.columnNames[2] = "PART_NO"; 
    OrderRec.columnNames[3] = "QTY"; 
} 

public class DoWork<T> where T : BaseRecord { 
    public void DisplayColumnNames() { 
    foreach(string s in T.columnNames) 
     Console.Write("{0}", s); 
    } 
    public void DisplayItem(T t) { 
    for (int i=0; i<itemValues.Length; i++) { 
     Console.Write("{0}: {1}",t.columnNames[i],t.itemValues[i]) 
    } 
    } 
} 

würde ich jede abgeleitete Klasse wie seine eigene statische Array von Strings von Datenbankspaltennamen haben, und ich würde die generische Klasse gerne Greifen Sie auf dieses statische Element zu, ohne eine Instanz zu benötigen.

Aber es funktioniert nicht:
(A) columnNames ist das identische Array in BaseRec, PeopleRec und OrderRec. Ich kann columnNames nicht anders haben. BaseRec.columnNames.Length wäre 3, weil die columnNames in OrderRec zuletzt initialisiert wurden.
(B) Die Notation T.columnNames kompiliert nicht.

Irgendwelche Ideen, wie Sie das beheben können?

Antwort

3

Das Problem ist, dass Sie einige Daten mit den Typen zugeordnet werden soll, nicht mit Instanzen der Typen . Ich bin mir nicht sicher, ob es in C# eine gute Möglichkeit gibt, dies zu tun, aber eine Möglichkeit besteht darin, eine statische Dictionary<Type, string[]> auf BaseRecord zu verwenden. Ein Beispiel ist unten, können Sie dies auf, indem Sie einige generische statische Mitglieder auf BaseRecord zur Initialisierung/Zugriff auf die Datensatznamen (und fügen Sie einige Fehlerprüfung ...) versäubern könnte:

using System; 
using System.Collections.Generic; 

namespace Records 
{ 
    public class BaseRecord 
    { 
     public bool isDirty; 
     public object[] itemArray; 

     public static Dictionary<Type, string[]> columnNames = new Dictionary<Type, string[]>(); 
    } 

    public class PeopleRec : BaseRecord 
    { 
     static PeopleRec() 
     { 
      string[] names = new string[2]; 
      names[0] = "FIRST_NAME"; 
      names[1] = "LAST_NAME"; 
      BaseRecord.columnNames[typeof(PeopleRec)] = names; 
     } 
    } 

    public class DoWork<T> where T : BaseRecord 
    { 
     public void DisplayColumnNames() 
     { 
      foreach (string s in BaseRecord.columnNames[typeof(T)]) 
       Console.WriteLine("{0}", s); 
     } 

     public void DisplayItem(T t) 
     { 
      for (int i = 0; i < t.itemArray.Length; i++) 
      { 
       Console.WriteLine("{0}: {1}", BaseRecord.columnNames[typeof(T)][i], t.itemArray[i]); 
      } 
     } 
    } 

    class Program 
    { 
     public static void Main() 
     { 
      PeopleRec p = new PeopleRec 
      { 
       itemArray = new object[] { "Joe", "Random" } 
      }; 

      DoWork<PeopleRec> w = new DoWork<PeopleRec>(); 
      w.DisplayColumnNames(); 
      w.DisplayItem(p); 
     } 
    } 
} 
+0

Danke Dave. Ich überlegte, ob ich ein Dictionary als letzten Ausweg verwenden sollte (einfach aufgrund des geringen zusätzlichen Leistungsaufwands), aber ich dachte nicht daran, den Typ (T) als Schlüsselfeld zu verwenden. Ihre Lösung ist sicherlich elegant. –

+0

Vielen Dank. Meine Lösung verwendet Attribute für die Klassen, aber es ist schmerzhaft langsam, wenn Sie es wiederholt treffen müssen (wegen der Reflektion) ... Ich könnte zu diesem wechseln, wenn ich es brauche, was sehr viel kostet. – Crisfole

1

Warum erstellen Sie nicht einfach separate Klassen von "Global Names", deren Mitglieder einfach statische Strings sind, die den Text enthalten, auf den Sie zugreifen müssen.

public static class OrderRecNames{ 
    public static const string OrderId = "ORDER_ID"; 
    public static const string Line = "LINE"; 
    public static const string PartNo = "PART_NO"; 
    public static const string Qty = "QTY"; 
} 

Edit:

Noch besser wäre es, machen jede statische Klasse von Global Names inneren Klassen der Klassen, die sie beschreiben, so dass anstelle von OrderRecNames.OrderId Sie OrderRec.Names.OrderId geben könnte, also der Zweck der Klasse ist sehr offensichtlich. Ich hoffe, das hilft!

Edit 2: (sehr spezifisch sein)

public class BaseRecord { 
    public bool isDirty; 
    public object [] itemArray; 
} 

public class PeopleRec : BaseRecord { 
    class Columns { 
     public static const string FirstName = "FIRST_NAME"; 
     public static const string LastName = "LAST_NAME"; 
    } 
} 

public class OrderRec : BaseRecord { 
    class Columns { 
     public static const string OrderId = "ORDER_ID"; 
     public static const string Line = "LINE"; 
     public static const string PartNo = "PART_NO"; 
     public static const string Qty = "QTY"; 
    } 
} 
+0

Da ich wollte, dass eine generische Klasse auf einer beliebigen Anzahl von Children von BaseRecord funktioniert, brauchte ich ein Array oder eine Liste oder etwas von den Feldern, und nicht benannte Felder (wie in, ich hätte keine Felder mit Vorname, Nachname , etc.). Könnte ich auch auf die Columns-Klasse wie T.Columns.OrderID in einer generischen Klasse zugreifen? –

1

Arrays sind Referenztypen. Fügen Sie einfach eine nicht statische Eigenschaft in die Basis ein und implementieren Sie sie in Ihren abgeleiteten Klassen, indem Sie ein statisches Element für alle Instanzen erstellen, auf die verwiesen wird. Der Overhead ist wirklich ziemlich klein. Ich verspreche, du wirst es nicht bemerken.

Oh, und ein ReadOnlyCollection<string> wäre besser als ein Array für diese Namen. Etwas wie folgt aus:

public class BaseRecord { 
    public bool isDirty; 
    public IEnumerable<Object> items; 
    public ReadOnlyCollection<string> columnNames;  
} 

oder

public class BaseRecord { 
    public bool isDirty; 
    public IList<Object> items; 

    public Object this[int index] 
    { 
    get { return items[index];} 
    set { items[index] = value;} 
    } 

    public ReadOnlyCollection<string> columnNames;  
} 

oder mit C# 4 (assuming my understanding of dynamic is correct):

public class BaseRecord { 
    public bool isDirty; 
    public IList<dynamic> items; 

    public dynamic this[int index] 
    { 
    get { return items[index];} 
    set { items[index] = value;} 
    } 

    public ReadOnlyCollection<string> columnNames;  
} 

Wirklich aber, frage ich mich, um die Vorteile dieser Klasse. Die Schnittstelle, die es bereitstellt, gibt Ihnen nur un-typed Zugriff auf die tatsächlichen Daten, und das ist nicht sehr nützlich. Sie könnten versuchen, es mit Generika zu beheben, aber dann am Ende mit etwas wie folgt auf:

public Record<T> 
{ 
    public bool isDirty; 
    public ReadOnlyCollection<string> columnNames; 
    public T data; 
} 

wo jedes „T“ mit den Feldern aus einer einzigen Tabelle eine ganz andere Klasse. Es gibt Ihnen keine gute Syntax, und wenn Sie diese anderen Klassen implementieren, können Sie genauso gut die Spaltennamen und das isDirty-Element direkt anheften.

Es scheint mir, dass eine bessere Lösung ist, wenn ich zuvor eine "Schnittstelle" erwähnt habe.Was Sie wirklich wollen, sieht eher so etwas wie dieses:

public IRecord 
{ 
    bool isDirty; 
    ReadOnlyCollection<string> columnNames; 
} 

oder vielleicht:

public IRecord 
{ 
    bool isDirty; 
    ReadOnlyCollection<string> columnNames; 

    dynamic this[int index] 
    { 
     get; 
     set; 
    } 
} 
+0

Ich freue mich über Ihre Antwort. Ich bin jedoch besorgt über die Verdoppelung der Speicher benötigt. Wenn jedes IRecord oder BaseRecord oder was auch immer sowohl Daten als auch das Array columnNames hat, verdoppelt es (oder mehr) die Größe. Ich stoße bereits an die Grenzen des Gedächtnisses für meine Anwendung. –

+0

@Marc - Sie haben einen wichtigen Punkt verpasst: Ich ** ** ** schlage vor, dass Sie für jeden IRecord eine Kopie des Arrays behalten! Arrays sind Referenztypen. Ich schlage vor, dass Sie eine eigene statische Sammlung für Ihre IRecord-Typen erstellen und Ihre columnNames-Eigenschaft mit einem Verweis auf diese statische Sammlung implementieren. Das sind nur 16 Bytes pro Instanz. Ich verspreche, dass deine App damit umgehen kann. –

+0

Joel: Ich habe den Punkt sicherlich vermisst. Sie sagen, erstellen Sie die Zeichenfolge von Spaltennamen und speichern Sie sie irgendwo, und jede Instanz von IRecord zeigen (siehe) dieses Array. Ich habe es vermisst, als ich es zum ersten Mal gelesen habe. Tatsächlich werden 16 Bytes pro Instanz nicht verpasst. Vielen Dank! –

-1

Wenn Sie die statische gehabt haben Variable würde ich dies tun:

public class BaseRecord 
    { 
    public bool isDirty; 
    public object[] itemArray; 
    public static string[] columnNames; 

    public void DisplayColumnNames() 
    { 
     foreach (string s in columnNames) 
      Console.Write("{0}\n", s); 
    } 

    public void DisplayItem() 
    { 
     for (int i = 0; i < itemArray.Length; i++) 
     { 
      Console.Write("{0}: {1}\n", columnNames[i], itemArray[i]); 
     } 
    } 
    } 

    public class PeopleRec : BaseRecord 
    { 
    public PeopleRec() 
    { 
    } 
    } 

    public class OrderRec : BaseRecord 
    { 
    public OrderRec() 
    { 
    } 
    } 

    public static void Main() 
    { 
    PeopleRec.columnNames = new string[2]; 
    PeopleRec.columnNames[0] = "FIRST_NAME"; 
    PeopleRec.columnNames[1] = "LAST_NAME"; 

    OrderRec.columnNames = new string[4]; 
    OrderRec.columnNames[0] = "ORDER_ID"; 
    OrderRec.columnNames[1] = "LINE"; 
    OrderRec.columnNames[2] = "PART_NO"; 
    OrderRec.columnNames[3] = "QTY"; 

    BaseRecord p = new PeopleRec(); 
    p.DisplayColumnNames(); 
    p.itemArray = new object[2]; 
    p.itemArray[0] = "James"; 
    p.itemArray[1] = "Smith"; 
    p.DisplayItem(); 

    BaseRecord o = new OrderRec(); 
    o.DisplayColumnNames(); 
    o.itemArray = new object[4]; 
    o.itemArray[0] = 1234; 
    o.itemArray[1] = 1; 
    o.itemArray[2] = 39874; 
    o.itemArray[3] = 70; 
    o.DisplayItem(); 
    } 

Sonst würde ich sie auf öffentliche Eigenschaften (keine statisch) ändern und sie nur auf das erzeugte abgeleitete Klasse Array hinzufügen (entfernen sie den statischen Aufruf mit einer Eigenschaft Anruf).

+0

Dies funktioniert überhaupt nicht - das statische Array von Spaltennamen lebt in der Basisklasse und wird überschrieben, wenn eine der Unterklassen instanziiert wird (erstellen Sie eine PeopleRec, dann eine OrderRec, und überprüfen Sie jedes der erstellten Instanzen statisch) Eigenschaften). Außerdem sollten Sie diese Logik wahrscheinlich nicht zum Einrichten dieser Namen im Konstruktor verwenden, da sich die Spaltennamen während 99,9% der Zeit während der Ausführung nicht ändern. – Pwninstein