C#中的静态成员继承

6

我有许多反映数据库中表的类。我希望有一个基类具有一些基本功能(例如,它会有一个“isDirty”标志),以及一个静态字符串数组,其中包含作为数据库中列名出现的名称。以下代码不起作用,但说明了我想要做的事情:

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])
    }
  }
}

我希望每个派生类都有自己的静态字符串数组,包含数据库列名。我希望通用类可以访问这个静态成员,而不需要实例。但是它不能正常工作:
(A)columnNames是BaseRec、PeopleRec和OrderRec中相同的数组。我不能让columnNames不同。BaseRec.columnNames.Length将为3,因为OrderRec中的columnNames最后初始化。
(B)T.columnNames表示法无法编译。
你有什么解决方法吗?
4个回答

3
问题在于您想将一些数据与类型相关联,而不是与类型的实例相关联。我不确定在C#中是否有一种简洁的方法来做到这一点,但一个可能的解决方法是在“BaseRecord”上使用静态Dictionary<Type,string[]>。下面是一个示例,您可以通过在“BaseRecord”上添加一些通用的静态成员来初始化/访问记录名称(并添加一些错误检查...)来使其更加整洁:
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);
        }
    }
}

谢谢Dave。我考虑过使用字典作为最后的手段(仅仅是因为稍微多一点性能开销),但我没有想到可以使用typeof(T)作为键字段。你的解决方案确实很优雅。 - Marc Meketon
非常感谢。我的解决方案使用类上的属性,但如果需要反复调用它(因为反射),它会非常缓慢...如果我最终需要经常使用它,我可能会转换到这个。 - Chris Pfohl

1
为什么不创建“全局名称”的单独类,其成员只是包含您需要访问的文本的静态常量字符串。
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";
}

编辑:

更好的做法是,将全局名称的每个静态类都作为它们所描述的类的内部类,这样你就可以输入OrderRec.Names.OrderId而不是OrderRecNames.OrderId,从而使类的目的非常明显。希望这有所帮助!

编辑2:(非常具体)

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

因为我想要一个通用类能够处理任意数量的BaseRecord子类,所以我需要有一个字段的数组、列表或类似的东西,而不是命名字段(如FirstName、LastName等)。另外,在一个通用类中,我能否像T.Columns.OrderID一样访问“Columns”类? - Marc Meketon

1

数组是引用类型。只需在基类中包含一个非静态属性,并通过为所有实例创建静态成员来在派生类中实现它。开销真的非常小。我保证你不会注意到。

哦,对于那些名称,ReadOnlyCollection<string>比数组更好。像这样:

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

或者

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

或者使用C# 4(假设我对dynamic的理解是正确的):

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

其实,我对这个类的好处有所质疑。它提供的接口只能让你以未经类型化的方式访问实际数据,这并不是很有用。你可以尝试使用泛型来解决这个问题,但最终可能会得到像这样的东西:
public Record<T>
{
    public bool isDirty;
    public ReadOnlyCollection<string> columnNames;
    public T data;
}

每个“T”都是一个完全不同的类,具有来自单个表的字段。它不会给你良好的语法,在您实现这些其他类之时,您可能会直接将列名和isDirty成员添加到其中。

在我之前提到“接口”的时候,似乎更好的解决方案是这样的:您真正想要的看起来更像这样:

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

或者也许

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

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

我感谢你的回答。然而,我担心会使存储需求翻倍。如果每个IRecord或BaseRecord或其他记录类型都有它自己的数据和columnNames数组,那么它将使大小翻倍(甚至更多)。我的应用程序已经接近内存极限了。 - Marc Meketon
@Marc - 你忽略了一个重要的点:我并不是建议你为每个IRecord保留一个数组副本!数组是引用类型。我建议你为你的IRecord类型构建一个单独的静态集合,并使用对该静态集合的引用来实现你的columnNames属性。这只需要每个实例16个字节。我保证你的应用程序可以处理这个。 - Joel Coehoorn
乔尔:我确实错过了重点。你的意思是创建列名的字符串并将其存储在某个地方,然后让每个IRecord实例指向(引用)该数组。我一开始读的时候没有注意到这一点。确实,每个实例的16字节不会被忽视。谢谢! - Marc Meketon

-1
如果你必须使用静态变量,我会这样做:
  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();
  }

否则,我会将它们更改为公共属性(非静态),并将它们添加到创建的派生类数组中(用属性调用替换静态调用)。

这根本行不通——列名的静态数组存在于基类中,并且在任何子类实例化时都会被覆盖(创建PeopleRec,然后创建OrderRec,并检查每个已创建实例的静态属性)。此外,您可能不应该在构造函数中设置这些名称的逻辑,因为99.9%的情况下,在执行期间列名不会更改。 - Mark Carpenter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接