有没有更简洁的方法来表达C#中的这个成语?

时间:2013-04-15 09:39:33

标签: c# coding-style value-type

我在项目中使用结构,如下所示:

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 个答案:

答案 0 :(得分:2)

就个人而言,我认为普通旧数据结构的习语被大大低估了。将状态封装在公共字段以外的任何形式的可变结构都是有问题的,但有时将用管道胶带粘在一起的固定变量集合绑定在一起,这样它们就可以作为一个单元传递。一个普通的数据结构非常适合这种用法;它的行为类似于用胶带粘在一起的固定变量集合,因为它就是 。人们可以通过一些工作来提出一个不可变的类,它需要缓慢且难以阅读的代码来执行任何操作,或者通过一些更多的工作来提出一些仍然缓慢但不那么不美观的东西;人们也可以用模仿这种类的方式对结构进行编码。然而,在许多情况下,通过所有这些努力的唯一影响是,如果一个人只使用PODS,那么一个人的代码将会更慢,更不清晰。

需要理解的关键是像struct PersonInfo { public string Name, SSN; public Date Birthdate; }之类的POD使代表一个人。它代表一个可以容纳两个字符串和一个日期的空间。如果有人说var fredSmithInfo = myDatabase.GetPersonInfo("Fred Smith");,那么FredSmithInfo.BirthDate并不代表弗雷德史密斯的出生日期;它表示Date类型的变量,它最初加载了对GetPersonInfo的调用返回的值 - 但与Date类型的任何其他变量一样,可以更改为包含任何其他变量日期。

答案 1 :(得分:1)

这就像你要得到的一样整洁。对我来说似乎并不特别惹人讨厌。

虽然在你正在做position.X + 1的情况下,有一些东西可以更简洁:

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

这将为您提供修改后的X值,但不会修改Y值。

答案 2 :(得分:0)

可以将此方法视为prototype pattern的变体,其中重点在于具有模板结构而不是避免新实例的成本。设计的好坏取决于您的背景。如果您可以使语法背后的消息清晰(我认为您使用的名称With有点不明确;可能像CreateVariantCreateMutant这样会使意图更清晰),我认为这是一种合适的方法。

答案 3 :(得分: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);
  }
}