2010-02-01 41 views
6

Tengo varias clases que reflejan tablas en una base de datos. Me gustaría tener una clase base que tenga alguna funcionalidad básica (por ejemplo, que tenga un indicador "isDirty") y una matriz estática de cadenas con los nombres de las columnas tal como aparecen en la base de datos. El código siguiente no funciona, pero ilustra lo que me gustaría hacer:Herencia de miembros estáticos en 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]) 
    } 
    } 
} 

Me gustaría que cada clase derivada a tener su propia matriz estática de cadenas de nombres de columna de base de datos, y me gustaría que la clase genérica de acceda a este miembro estático sin la necesidad de una instancia.

Pero no funciona:
(A) columnNames es la matriz idéntica en BaseRec, PeopleRec y OrderRec. No puedo hacer que columnNames sea diferente. BaseRec.columnNames.Length sería 3 porque columnNames en OrderRec se inicializa al final.
(B) La notación T.columnNames no compila.

¿Alguna idea sobre cómo solucionar esto?

Respuesta

3

El problema que es que desea asociar unos datos con los tipos, no con las instancias de los tipos . No estoy seguro de que exista una forma clara de hacerlo en C#, pero una posibilidad es usar un Dictionary<Type, string[]> estático en BaseRecord. Un ejemplo se encuentra por debajo, podría sobrehilarlos esto mediante la adición de algunos miembros estáticos genéricos en BaseRecord para inicializar/acceder a los nombres de los registros (y añadir un poco de comprobación de errores ...):

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

Gracias Dave. Consideré usar un diccionario como último recurso (simplemente debido a la pequeña sobrecarga de rendimiento adicional), pero no pensé en usar el tipo de letra (T) como campo clave. Su solución ciertamente es elegante. –

+0

Muchas gracias. Mi solución usa Atributos en las clases, pero es muy lento si necesitas golpearla repetidamente (debido a la reflexión) ... Podría cambiar a esto si llego a necesitar esto mucho. – Crisfole

1

¿Por qué no simplemente crea clases separadas de "Nombres globales", cuyos miembros son simplemente cadenas constantes estáticas que contienen el texto que necesita acceder.

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

Editar:

Mejor aún, hacer que cada clase estática del mundial de nombres, clases internas de las clases que describen, por lo que en lugar de OrderRecNames.OrderId, podría escribir OrderRec.Names.OrderId, por lo que el objetivo de la clase es muy obvio. ¡Espero que ayude!

Editar 2: (a ser muy específico)

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

Porque quería una clase genérica para trabajar en cualquier número de hijos de BaseRecord, necesitaba tener una matriz o lista o algo de los campos, y campos sin nombre (como en, no podía tener campos llamados Nombre, Apellido , etc.) Además, ¿podría acceder a la clase "Columnas" como T.Columns.OrderID en una clase genérica? –

1

Las matrices son los tipos de referencia. Solo incluya una propiedad no estática en la base e impleméntela en sus clases derivadas creando un miembro estático para todas las instancias a las que se refiere. La sobrecarga es realmente muy pequeña. Te prometo que no lo notarás.

Ah, y un ReadOnlyCollection<string> sería mejor que una matriz para esos nombres. Algo como esto:

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

Realmente, sin embargo, que cuestionan los beneficios de esta clase. La interfaz que proporciona solo le brinda acceso sin tipeo a los datos reales, y eso no es muy útil. Usted puede tratar de solucionarlo con los genéricos, pero luego terminar con algo como esto:

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

donde cada "T" es una clase completamente diferente con los campos de una sola tabla. No le da una buena sintaxis, y para el momento en que implemente esas otras clases, también puede virar los nombres de las columnas y el miembro Sucio directamente.

Me parece que una mejor solución es cuando mencioné anteriormente una "interfaz".Lo que realmente quiere se ve algo de la misma familia:

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

o quizá:

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

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

Agradezco su respuesta. Sin embargo, me preocupa duplicar el almacenamiento necesario. Si cada IRecord o BaseRecord o lo que sea tiene sus datos y la matriz columnsNames, está doblando (o más) el tamaño. Ya estoy llegando a los límites de la memoria para mi aplicación. –

+0

@Marc: te perdiste un punto importante: ¡no estoy ** sugiriendo ** que guardes una copia de la matriz para cada IRecord! Las matrices son tipos de referencia. Sugiero que cree una colección estática separada para sus tipos de IRecord e implemente su propiedad columnNames con una referencia a esa colección estática. Eso es solo 16 bytes por instancia. Prometo que su aplicación puede manejar eso. –

+0

Joel: Ciertamente no entendí el punto. Usted está diciendo crear la cadena de nombres de columna y almacenarla en alguna parte, y tener cada instancia de punto IRecord (referirse a) esa matriz. Me lo perdí cuando lo leí por primera vez. De hecho, no se perderán 16 bytes por instancia. ¡Gracias! –

-1

Si tenía que tener la estática variables me gustaría hacer esto:

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

de lo contrario, me gustaría cambiar a las propiedades públicas (ninguno estática) y hay que añadirlas a la matriz creada clase derivada (quitar la llamada estática con una llamada propiedad).

+0

Esto no funcionará en absoluto: la matriz estática de nombres de columna vive en la clase base, y se sobrescribirá cuando se crea una instancia de alguna de las subclases (crear un PeopleRec, luego un OrderRec e inspeccionar cada una de las instancias creadas estáticas) propiedades). Además, probablemente no deberías tener esa lógica para configurar estos nombres en el constructor, ya que el 99.9% de las veces, los nombres de las columnas no cambian durante la ejecución. – Pwninstein

Cuestiones relacionadas