4

次のように、プロジェクトで構造体を使用しています。

struct Position
{
    public int X { get; private set; }
    public int Y { get; private set; }
    // etc
}

任意に変更されたプロパティを持つ構造体の変更されたコピーを作成できるメソッドを追加したいと思います。たとえば、これを使用すると便利です。

var position = new Position(5, 7);
var newPos = position.With(X: position.X + 1);

このイディオムはハッキーですか?これをサポートするより良い方法はありますか?

public Position With(int? X = null, int? Y = null)
{
    return new Position(X ?? this.X, Y ?? this.Y);
}

編集:不明な場合、構造体は不変です。いくつかの値を変更して新しい値を作成したいだけです。ちなみに、これは Haskell のレコードのシンタックス シュガーに非常によく似ていますnewPos = oldPos { x = x oldPos + 1 }。これは、そのようなイディオムが C# で役立つかどうかについて、少し実験的なことです。

4

4 に答える 4

2

個人的には、単純なデータ構造体のイディオムは非常に過小評価されていると考えています。パブリック フィールド以外の状態をカプセル化する変更可能な構造体には問題がありますが、固定された変数のコレクションをダクト テープでまとめてバインドし、それらを 1 つのユニットとして渡すことができると便利な場合があります。単純な古いデータ構造体は、その使用法に最適です。それはそれが何であるかであるため、ダクトテープでくっついた変数の固定コレクションのように動作します. 何かを行うには遅くて読みにくいコードを必要とする不変のクラスを考え出すことができます。そのようなクラスを模倣するような方法で構造をコーディングすることもできます。ただし、多くの場合、そのすべての作業を行った場合の唯一の影響は、単純に PODS を使用した場合よりもコードが遅くなり、明確さが失われることです。

理解する必要がある重要なことは、PODS のようなものは人を表してstruct PersonInfo { public string Name, SSN; public Date Birthdate; }ないということです。2 つの文字列と日付を保持できるスペースを表します。という場合はvar fredSmithInfo = myDatabase.GetPersonInfo("Fred Smith");FredSmithInfo.BirthDateフレッド スミスの生年月日を表していません。Dateこれは、 --への呼び出しによって返された値が最初に読み込まれるtype の変数を表しますが、 typeGetPersonInfoの他の変数と同様に、Date他の日付を保持するように変更できます。

于 2013-04-15T20:48:57.623 に答える
1

それはあなたが得ようとしているのと同じくらいきちんとした方法です. 私には特にハックしているようには見えません。

ただやっているだけの場合は、次position.X + 1のようなものがある方がきれいです。

var position = new Position(5,7);
var newPos = position.Add(new Position(1,0));

これにより、変更された X 値が得られますが、変更された Y 値は得られません。

于 2013-04-15T09:43:27.723 に答える
0

式ベースのフォームも追加しています。それが構造体であるという事実のために実行する必要がある恐ろしいボクシング/アンボクシングに注意してください。

しかし、ご覧のとおり、フォーマットは非常に優れています。

var p2 = p.With(t => t.X, 4);
var p3 = p.With(t => t.Y, 7).With(t => t.X, 5); // Yeah, replace all the values :)

そして、この方法はあらゆる種類のタイプに実際に適用できます。

public void Test()
{
  var p = new Position(8, 3);

  var p2 = p.With(t => t.X, 4);
  var p3 = p.With(t => t.Y, 7).With(t => t.X, 5);

  Console.WriteLine(p);
  Console.WriteLine(p2);
  Console.WriteLine(p3);
}

public struct Position
{
  public Position(int X, int Y)
  {
    this._X = X; this._Y = Y;
  }

  private int _X; private int _Y;
  public int X { get { return _X; } private set { _X = value; } }
  public int Y { get { return _Y; } private set { _Y = value; } }

  public Position With<T, P>(Expression<Func<Position, P>> propertyExpression, T value)
  {
    // Copy this
    var copy = (Position)this.MemberwiseClone();
    // Get the expression, might be both MemberExpression and UnaryExpression
    var memExpr = propertyExpression.Body as MemberExpression ?? ((UnaryExpression)propertyExpression.Body).Operand as MemberExpression;
    if (memExpr == null)
      throw new Exception("Empty expression!");

    // Get the propertyinfo, we need this one to set the value
    var propInfo = memExpr.Member as PropertyInfo;
    if (propInfo == null)
      throw new Exception("Not a valid expression!");

    // Set the value via boxing and unboxing (mutable structs are evil :) )
    object copyObj = copy;
    propInfo.SetValue(copyObj, value); // Since struct are passed by value we must box it
    copy = (Position)copyObj;
    // Return the copy
    return copy;
  }

  public override string ToString()
  {
    return string.Format("X:{0,4} Y:{1,4}", this.X, this.Y);
  }
}
于 2013-04-15T11:56:51.943 に答える