valueTypesフィールドとボクシングを持つクラス
-
02-07-2019 - |
質問
ジェネリックを試し、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
}
}
コメントからわかるように、ここでは構造体の複製に関する問題はほとんどありません。これの簡単な解決策は、Columnをクラスに変更することですが、私のテストからは、メモリ使用量が(各オブジェクトメタデータのために)〜40%増加するように思えますが、これは受け入れられません。
だから私の質問は次のとおりです。異なる構造化オブジェクト/レコードで機能するメソッドを作成する他のアイデアはありますか?おそらく、F#コミュニティの誰かが、そのような問題を関数型言語でどのように解決し、それがパフォーマンスとメモリ使用量にどのように影響するかを提案できるでしょう。
編集:
sfgマクロに関する提案をありがとう。
Visual Studio 2008には、T4と呼ばれる組み込みの(しかしあまり知られていない)テンプレートエンジンがあります。全体のポイントは、プロジェクトに「.tt」ファイルを追加し、すべてのクラスを検索し、何らかの形でレコードであるクラスを認識するテンプレートを作成し(たとえば、実装するインターフェイスによって)、HasChangesおよびAcceptChanges( )クラスに含まれる列のみを呼び出します。
便利なリンク:
VSのT4エディター
リンクとブログT4に関するチュートリアル
EnvDTEを使用してプロジェクトファイルを読み取る例のブログエントリ
解決
関数型言語の例を要求したように、 Lispでは、マクロを使用してコードを生成することにより、列を追加するたびにすべてのコードが記述されないようにすることができます。悲しいことに、私はそれがC#で可能だとは思わない。
パフォーマンスの観点から:マクロはコンパイル時に評価されます(したがって、コンパイルはわずかに遅くなります)が、実行時のコードは想定したものと同じであるため、実行時にスローダウンは発生しません手動で書き込みます。
クローンバージョンへの書き込みを避けたい場合は、識別子によって構造体に直接アクセスする必要があるため、元のAcceptChanges()を受け入れる必要があると思います。
言い換えれば、あなたはあなたのためにプログラムを書くためのプログラムを必要とします、そして、私はC#でそれをする方法を知りません。例:反射)。
他のヒント
リフレクションを使用してメンバーを反復処理し、HasChangesおよびAcceptChangesを呼び出すことができます。 Recordクラスは、リフレクションメタデータを静的として保存できるため、インスタンスごとのメモリオーバーヘッドはありません。ただし、実行時のパフォーマンスコストは非常に大きくなります。また、列をボックス化およびボックス化解除して、コストをさらに増やす可能性があります。
正直なところ、これらの Column
を本当にクラスにしたいのですが、クラスに関連付けられたランタイムコストを支払いたくないので、それらを構造体にしようとしています。あなたが望むことをするエレガントな方法を見つけるとは思わない。構造体は値型であると想定されており、参照型のように動作させる必要があります。
IColumn
の配列に列を効率的に保存できないため、配列アプローチはうまく機能しません。コンパイラーは、 IColumn
配列が構造体のみを保持することを知る方法がありません。実際には、保持しようとすると、それは助けにはなりません。 。誰かが AcceptChanges()
または HasChanges()
を呼び出すたびに、いずれにせよ構造体のボクシングとクローン作成が行われるため、 Column
クラスの代わりに構造体を使用すると、多くのメモリを節約できます。
ただし、おそらく Column
を配列に直接格納し、enumでインデックスを作成できます。例:
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#コンパイラが手元にないので、それが機能するかどうかを確認することはできませんが、基本的な考え方は機能します。詳細がすべて正しくなくても機能します。しかし、私は先に進み、それらをクラスにします。とにかくそれを払っています。
あなたが本当にやりたいことをする唯一の方法は、Reflectionを使用することです。これでもボックス/アンボックスになりますが、クローンをフィールドに保存して 、実際の値にすることができます。
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
}
}
これについてはどうですか:
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);
}