Is there a cleaner way to introduce this idiom in C #?

I use the structure in the project, for example:

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

I would like to add a method that allows me to create a modified copy of the structure with arbitrarily changed properties. For example, it would be convenient to use this:

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

Is this a hacky idiom? Are there any better ways to support this?

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

Edit: in case this is unclear, the structure is immutable, I just want to create a new value with some changed values. By the way, this is very similar to Haskell's syntactic sugar for entries, where you can write newPos = oldPos { x = x oldPos + 1 } . This is a little experimental regarding whether such an idiom is useful in C #.

+4
source share
4 answers

Personally, I believe that the idiom of a simple data-to-data structure is significantly underestimated. Volatile structures that encapsulate a state in anything other than public fields are problematic, but it is sometimes useful to link together a fixed set of variables glued together with duct tape so that they can be transferred as a unit. The simple data-data structure is ideal for use; it behaves like a fixed set of variables glued together with duct tape, as that is what it is. With some work, you can come up with an immutable class that requires slow and hard-to-read code to do something, or with some extra work, come up with something that is still slow but not so unaesthetic; you can also code structures in such a way as to mimic such classes. In many cases, however, the only effect of all this effort is that one code will be slower and less transparent than if it just used PODS.

The key value to understand is that PODS, for example struct PersonInfo { public string Name, SSN; public Date Birthdate; } struct PersonInfo { public string Name, SSN; public Date Birthdate; } struct PersonInfo { public string Name, SSN; public Date Birthdate; } does not represent a person. It represents a space that can contain two lines and a date. If we say var fredSmithInfo = myDatabase.GetPersonInfo("Fred Smith"); , then FredSmithInfo.BirthDate does not represent the birth date of Fred Smith; it represents a variable of type Date , which is initially loaded with the value returned by the GetPersonInfo call, but, like any other variable of type Date , it can be modified to store any other date.

+2
source

It's as neat as you pack. It does not seem to me that this is especially harmful.

Although in cases where you just do position.X + 1 , it would be more neat to have something like:

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

Which will give you a changed value of X, but no changed value of Y.

+1
source

You can consider this approach as a variant of the prototype template, where the focus is on creating the structure of the templates, rather than reducing the cost of new copies. Whether the design is good or bad depends on your context. If you can make the message behind the syntax understandable (I think the With name that you use is a little non-specific, maybe something like CreateVariant or CreateMutant will make the intent clearer), I would consider it a suitable approach.

0
source

I am adding an expression based form. Pay attention to the terrible boxing / unboxing that needs to be done because it is a structure.

But, as you can see, the format is pretty nice:

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

And this method really applies to all types of types.

 public void Test() { var p = new Position(8, 3); var p2 = p.With(t => tX, 4); var p3 = p.With(t => tY, 7).With(t => tX, 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); } } 
0
source

All Articles