【问题标题】:class with valueTypes fields and boxing具有 valueTypes 字段和装箱的类
【发布时间】:2008-09-26 21:05:09
【问题描述】:

我正在试验泛型,我正在尝试创建类似于 Dataset 类的结构。
我有以下代码

public struct Column<T>
{
    T value;
    T originalValue;

    public bool HasChanges
    {
        get { return !value.Equals(originalValue); }
    }

    public void AcceptChanges()
    {
        originalValue = value;
    }
}

public class Record
{
    Column<int> id;
    Column<string> name;
    Column<DateTime?> someDate;
    Column<int?> someInt;

    public bool HasChanges
    {
        get
        {
            return id.HasChanges | name.HasChanges | someDate.HasChanges | someInt.HasChanges;
        }
    }

    public void AcceptChanges()
    {
        id.AcceptChanges();
        name.AcceptChanges();
        someDate.AcceptChanges();
        someInt.AcceptChanges();
    }
}

我遇到的问题是,当我添加新列时,我还需要在 HasChanges 属性和 AcceptChanges() 方法中添加它。这只是要求进行一些重构。
所以我想到的第一个解决方案是这样的:

public interface IColumn
{
    bool HasChanges { get; }
    void AcceptChanges();
}

public struct Column<T> : IColumn {...}
public class Record
{
    Column<int> id;
    Column<string> name;
    Column<DateTime?> someDate;
    Column<int?> someInt;

    IColumn[] Columns { get { return new IColumn[] {id, name, someDate, someInt}; }}

    public bool HasChanges
    {
        get
        {
            bool has = false;
            IColumn[] columns = Columns;            //clone and boxing
            for (int i = 0; i < columns.Length; i++)
                has |= columns[i].HasChanges;
            return has;
        }
    }

    public void AcceptChanges()
    {
        IColumn[] columns = Columns;            //clone and boxing
        for (int i = 0; i < columns.Length; i++)
            columns[i].AcceptChanges();         //Here we are changing clone
    }
}

正如您从 cmets 中看到的,我们在结构克隆方面几乎没有问题。对此的简单解决方案是将 Column 更改为类,但从我的测试来看,它似乎将内存使用量增加了约 40%(因为每个对象元数据),这对我来说是不可接受的。

所以我的问题是:有没有人有任何其他想法如何创建可以在不同结构化对象/记录上工作的方法?也许 F# 社区的某个人可以建议如何用函数式语言解决这些问题,以及它如何影响性能和内存使用。

编辑:
sfg 感谢有关宏的建议。
在 Visual Studio 2008 中有一个内置的(但不为人知的)模板引擎,称为 T4。重点是将“.tt”文件添加到我的项目中并创建一个模板,该模板将搜索我的所有类,以某种方式识别记录的那些(例如通过它们实现的某些接口)并使用 HasChanges 和 AcceptChanges 生成部分类( ) 将只调用类包含的列。

一些有用的链接:
T4 Editor for VS
Blog with links and tutorials about T4
Blog entry with example that uses EnvDTE to read project files

【问题讨论】:

    标签: c# data-structures t4


    【解决方案1】:

    正如您要求的函数式语言示例;在 lisp 中,您可以通过使用宏为您编写代码来防止在每次添加列时编写所有代码。可悲的是,我认为这在 C# 中是不可能的。

    在性能方面:宏将在编译时进行评估(因此会稍微减慢编译速度),但不会导致运行时减速,因为运行时代码与您的代码相同手动编写。

    我认为您可能必须接受原始的 AcceptChanges(),因为如果您想避免写入克隆版本,则需要直接通过其标识符访问结构。

    换句话说:你需要一个程序来为你编写程序,而我不知道如何在 C# 中做到这一点,而不会比你将结构体切换到类(例如反射)。

    【讨论】:

      【解决方案2】:

      老实说,听起来您确实希望这些 Columns 成为类,但又不想支付与类相关的运行时成本,因此您试图将它们设为结构。我不认为你会找到一种优雅的方式来做你想做的事。结构应该是值类型,你想让它们表现得像引用类型。

      您无法将列有效地存储在IColumns 的数组中,因此任何数组方法都无法正常工作。编译器无法知道IColumn 数组将只保存结构,事实上,如果这样做也无济于事,因为您仍然试图在其中插入不同类型的结构。每次有人调用AcceptChanges()HasChanges() 时,你最终都会对你的结构进行装箱和克隆,所以我严重怀疑让你的Column 成为一个结构而不是一个类是否会为你节省很多内存。

      但是,您可以将您的Columns 直接存储在一个数组中,并使用枚举对其进行索引。例如:

      public class Record
      {
          public enum ColumnNames { ID = 0, Name, Date, Int, NumCols };
      
          private IColumn [] columns;
      
          public Record()
          {
              columns = new IColumn[ColumnNames.NumCols];
              columns[ID] = ...
          }
      
          public bool HasChanges
          {
              get
              {
                  bool has = false;
                  for (int i = 0; i < columns.Length; i++)
                      has |= columns[i].HasChanges;
                  return has;
              }
          }
      
          public void AcceptChanges()
          {
              for (int i = 0; i < columns.Length; i++)
                  columns[i].AcceptChanges();
          }
      }
      

      我手边没有 C# 编译器,所以我无法检查它是否可行,但基本想法应该可行,即使我没有正确了解所有细节。但是,我会继续为他们上课。反正你是要付钱的。

      【讨论】:

        【解决方案3】:

        您可以使用反射来迭代成员并调用 HasChanges 和 AcceptChanges。 Record 类可以将反射元数据存储为静态的,因此没有每个实例的内存开销。但是,运行时的性能成本将是巨大的——您最终可能还会对列进行装箱和拆箱,这会增加更多的成本。

        【讨论】:

          【解决方案4】:

          我能想到的唯一方法是使用反射。这仍然会装箱/拆箱,但它允许您将克隆存储回字段中,从而有效地使其成为真正的价值。

          public void AcceptChanges()
          {
              foreach (FieldInfo field in GetType().GetFields()) {
                  if (!typeof(IColumn).IsAssignableFrom(field.FieldType))
                      continue; // ignore all non-IColumn fields
                  IColumn col = (IColumn)field.GetValue(this); // Boxes storage -> clone
                  col.AcceptChanges(); // Works on clone
                  field.SetValue(this, col); // Unboxes clone -> storage
              }
          }
          

          【讨论】:

            【解决方案5】:

            这个怎么样:

            public interface IColumn<T>
            {
                T Value { get; set; }
                T OriginalValue { get; set; }
            }
            
            public struct Column<T> : IColumn<T>
            {
                public T Value { get; set; }
                public T OriginalValue { get; set; }
            }
            
            public static class ColumnService
            {
                public static bool HasChanges<T, S>(T column) where T : IColumn<S>
                {
                    return !(column.Value.Equals(column.OriginalValue));
                }
            
                public static void AcceptChanges<T, S>(T column) where T : IColumn<S>
                {
                    column.Value = column.OriginalValue;
                }
            }
            

            那么客户端代码是:

            Column<int> age = new Column<int>();
            age.Value = 35;
            age.OriginalValue = 34;
            
            if (ColumnService.HasChanges<Column<int>, int>(age))
            {
                ColumnService.AcceptChanges<Column<int>, int>(age);
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-07-02
              • 2016-11-23
              • 2022-01-02
              • 2012-03-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-06-10
              相关资源
              最近更新 更多