1

私はジェネリックを試していて、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 エディター ( EnvDTE を使用してプロジェクト ファイルを読み取る例を含む
T4 ブログ エントリに関するリンクとチュートリアルを含む)

4

5 に答える 5

1

関数型言語の例を求めたように。Lisp では、マクロを使用してコードをクランクアウトすることで、列を追加するたびにそのすべてのコードが書き込まれるのを防ぐことができます。残念ながら、C# ではそれができないと思います。

パフォーマンスの観点から: マクロはコンパイル時に評価されます (したがって、コンパイルが少し遅くなります) が、実行時のコードは手動で記述したものと同じであるため、実行時に速度が低下することはありません。

複製されたバージョンへの書き込みを避けたい場合は、識別子によって構造体に直接アクセスする必要があるため、元の AcceptChanges() を受け入れる必要があるかもしれません。

言い換えれば、あなたはあなたのためにプログラムを書くためのプログラムが必要であり、構造体をクラスに切り替えることによってこれまでよりも異常な長さになったりパフォーマンスを失うことなくC#でそれを行う方法がわかりません(リフレクションなど) .

于 2008-09-26T21:46:47.717 に答える
1

正直なところ、これらColumnの s をクラスにしたいが、クラスに関連するランタイム コストを払いたくないので、それらを構造体にしようとしているように思えます。あなたがやりたいことをするエレガントな方法を見つけるつもりはないと思います。構造体は値型であると想定されており、それらを参照型のように動作させたいとします。

Columns を の配列に効率的に格納することはできないIColumnため、配列アプローチはうまく機能しません。コンパイラーは、配列が構造体のみを保持することを知る方法がありません。IColumn実際、そこに詰め込もうとしているさまざまなタイプの構造体がまだあるため、保持していても役に立ちません。AcceptChanges()誰かがorを呼び出すたびHasChanges()に、とにかく構造体をボクシングして複製することになるのでColumn、クラスの代わりに構造体を作成することで多くのメモリを節約できるとは思えません。

ただし、おそらくs を配列に直接格納し、列挙型でインデックスを付けることができます。Column例えば:

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# コンパイラを持っていないので、それが機能するかどうかを確認することはできませんが、すべての詳細を正しく理解していなくても、基本的なアイデアは機能するはずです。ただし、先に進んでそれらをクラスにします。とにかくあなたはそれを払っています。

于 2008-09-26T22:00:39.010 に答える
0

これはどう:

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);
}
于 2008-09-27T05:39:41.637 に答える
0

リフレクションを使用してメンバーを反復処理し、HasChanges と AcceptChanges を呼び出すことができます。Record クラスはリフレクション メタデータを静的として格納できるため、インスタンスごとのメモリ オーバーヘッドはありません。ただし、実行時のパフォーマンス コストは非常に大きくなります。列をボックス化してボックス化解除することもあり、コストがさらに高くなります。

于 2008-09-26T21:32:43.647 に答える
0

あなたが本当にやりたいことを行うために私が考えることができる唯一の方法は、リフレクションを使用することです。これでもボックス化/ボックス化解除されますが、クローンをフィールドに戻すことができるため、効果的に真の値になります。

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
    }
}
于 2008-09-26T22:11:10.310 に答える