2010-02-01 13 views
6

Ho un numero di classi che riflettono le tabelle in un database. Mi piacerebbe avere una classe base che abbia alcune funzionalità di base (diciamo, avrebbe un flag "isDirty") e una matrice statica di stringhe con i nomi delle colonne così come appaiono nel database. Il seguente codice non funziona, ma illustra quello che vorrei fare:Elemento ereditario membro statico 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]) 
    } 
    } 
} 

Vorrei ciascuna classe derivata di avere un proprio array statico di stringhe di nomi di colonna di database, e vorrei che la classe generica per accedi a questo membro statico senza bisogno di un'istanza.

Ma non funziona:
(A) columnNames è l'array identico in BaseRec, PeopleRec e OrderRec. Non posso avere columnNames essere diverso. BaseRec.columnNames.Length sarebbe 3 perché il columnNames in OrderRec è inizializzato per ultimo.
(B) La notazione T.columnNames non viene compilata.

Qualche idea su come risolvere questo problema?

risposta

3

Il problema che è che si desidera associare alcuni dati con i tipi, non con le istanze dei tipi . Non sono sicuro che ci sia un modo pulito per farlo in C#, ma una possibilità è l'utilizzo di uno statico Dictionary<Type, string[]> su BaseRecord. Un esempio è al di sotto, si potrebbe neaten questo con l'aggiunta di alcuni membri statici generici sul BaseRecord per l'inizializzazione/accesso i nomi dei record (e aggiungere un po 'il controllo degli errori ...):

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

Grazie Dave. Ho considerato l'utilizzo di un dizionario come ultima risorsa (semplicemente a causa del piccolo sovraccarico di prestazioni extra), ma non ho pensato di utilizzare il tipoof (T) come campo chiave. La tua soluzione è sicuramente elegante. –

+0

Grazie mille. La mia soluzione utilizza Attributi sulle classi, ma è dolorosamente lenta se devi colpirla ripetutamente (a causa del riflesso) ... Potrei passare a questo se finisco per averne bisogno. – Crisfole

1

Perché non basta creare classi separate di "Nomi globali", i cui membri sono semplicemente stringhe costanti statiche contenenti il ​​testo che è necessario accedere.

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:

Meglio ancora, rendono ogni classe statica dei nomi globali classi interne delle classi che descrivono, così invece di OrderRecNames.OrderId, è possibile digitare OrderRec.Names.OrderId, così lo scopo della classe è molto evidente. Spero possa aiutare!

Edit 2: (per essere molto specifico)

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

Perché volevo che una classe generica funzionasse su qualsiasi numero di figli di BaseRecord, avevo bisogno di avere un array o elenco o qualcosa dei campi, e non campi con nome (come in, non avrei potuto avere campi chiamati FirstName, LastName , eccetera.). Inoltre, potrei accedere alla classe "Columns" come T.Columns.OrderID in una classe generica? –

1

Gli array sono tipi di riferimento. Basta includere una proprietà non statica nella base e implementarla nelle classi derivate creando un membro statico per tutte le istanze a cui fare riferimento. Il sovraccarico è davvero piuttosto piccolo. Prometto che non lo noterai.

Oh, e un ReadOnlyCollection<string> sarebbe meglio di un array per questi nomi. Qualcosa di simile a questo:

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

o

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

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

In realtà, però, mi chiedo i benefici di questa classe. L'interfaccia fornisce solo un accesso non digitato ai dati effettivi e questo non è molto utile. Si potrebbe provare a risolvere il problema con i generici, ma poi si finisce con qualcosa di simile:

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

dove ogni "T" è una classe completamente diverso con i campi da una singola tabella. Non ti dà una buona sintassi e, nel momento in cui implementerai quelle altre classi, potresti anche collegare direttamente i nomi delle colonne e il membro isDirty.

Mi sembra che una soluzione migliore sia quando ho menzionato in precedenza una "interfaccia".Che cosa si vuole veramente sembra qualcosa di più simile a questo:

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

o forse:

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

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

Apprezzo la tua risposta. Tuttavia, sono preoccupato di raddoppiare lo spazio necessario. Se ogni IRecord o BaseRecord o qualsiasi cosa ha sia i suoi dati sia l'array columnNames, raddoppia (o più) le dimensioni. Sto già colpendo i limiti della memoria per la mia applicazione. –

+0

@Marc - Hai perso un punto importante: non sto ** suggerendo di tenere una copia dell'array per ogni IRecord! Gli array sono tipi di riferimento. Ti sto suggerendo di creare una raccolta statica separata per i tuoi tipi di IRecord e di implementare la proprietà columnNames con un riferimento a tale raccolta statica. Questo è solo 16 byte per istanza. Prometto che la tua app possa gestirlo. –

+0

Joel: Certamente ho mancato il punto. Stai dicendo di creare la stringa di nomi di colonne e memorizzarla da qualche parte, e fare in modo che ogni istanza di IRecord punti (riferirsi a) a quella matrice. L'ho perso quando l'ho letto per la prima volta. In effetti, 16 byte per istanza non saranno persi. Grazie! –

-1

Se si doveva avere la statica variabile farei questo:

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

In caso contrario, li vorrei cambiare di proprietà pubbliche (nessuno statica) e semplicemente aggiungerli alla matrice classe derivata creato (rimuovere la chiamata statica con una chiamata di proprietà).

+0

Questo non funzionerà affatto - la matrice statica di nomi di colonne risiede nella classe base, e verrà sovrascritta quando una qualsiasi delle sottoclassi viene istanziata (creare un PeopleRec, quindi un OrderRec e ispezionare ciascuna istanza statica creata proprietà). Inoltre, probabilmente non dovresti avere quella logica per impostare questi nomi nel costruttore, dal momento che il 99,9% delle volte, i nomi delle colonne non cambiano durante l'esecuzione. – Pwninstein