вторник, 18 мая 2010 г.

Copy-And-Update in C#

Disclaimer: only for demonstration purposes :).

Immutability is natural for F# types; tuples, lists and records are immutable by default. Also records have special syntax “copy-and-update” that allows creation copy of existing record specifying only modified fields.

type DataObject = 
{ Id : int
Value : string
Object : DataObject2 }
and DataObject2 =
{ Price : decimal
PriceType : string }

let o1 = {Id = 100; Value = "Value"; Object = {Price = 10m; PriceType = "CleanPrice"}}
let o2 = {o1 with Id=500; Object = {o1.Object with Price = 500m} }
(*
val o1 : DataObject = {Id = 100;
Value = "Value";
Object = {Price = 10M;
PriceType = "CleanPrice";};}
val o2 : DataObject = {Id = 500;
Value = "Value";
Object = {Price = 500M;
PriceType = "CleanPrice";};}
*)

C# doesn't provide similar things, but using VS 2010 and combination of named and optional parameters we can achive almost the same level of expressiveness.

var o1 = new DataObject(100, "Value1", new DataObject2(10m, "CleanPrice"));
var o2 = o1.With(id: 500, obj: o1.Object.With(price: 500));
Console.WriteLine(o1);
Console.WriteLine(o2);
//Id = 100, Value = Value1, Object = (Price:10, PriceType=CleanPrice)
//Id = 500, Value = Value1, Object = (Price:500, PriceType=CleanPrice)

Pretty close to original, huh? Idea of implementation is very simple, we somehow need to distinguish missing values from entered. To achieve it we introduce struct Optional that will hold all input parameters

    public struct Optional<T>
{
public T Value;
public bool HasValue;

public Optional(T value)
{
HasValue = true;
Value = value;
}

public static implicit operator Optional<T>(T value)
{
return new Optional<T>(value);
}
public T ValueOrDefault(T defaultValue) { return HasValue ? Value : defaultValue; } }

With method in each class declare its arguments with type Optional<T> and default value default( Optional<T>). This is simple solution, all entered arguments shall be converted by implicit operator and thus have HasValue=true. Optional is struct because otherwise it is impossible to input null as user defined value, it shall be interpreted not as valid value but rather as missing one.

    public class DataObject
{
public int Id { get; private set; }
public string Value { get; private set; }
public DataObject2 Object { get; private set; }

public DataObject(int id, string value, DataObject2 o)
{
Id = id;
Value = value;
Object = o;
}

private DataObject()
{
}

public override string ToString()
{
return string.Format("Id = {0}, Value = {1}, Object = ({2})", Id, Value ?? "null", Object != null ? Object.ToString() : "null");
}

public DataObject With(
Optional<int> id = default(Optional<int>),
Optional<string> value = default(Optional<string>),
Optional<DataObject2> obj = default(Optional<DataObject2>)
)
{
return new DataObject
{
Id = id.ValueOrDefault(Id),
Value = value.ValueOrDefault(Value),
Object = obj.ValueOrDefault(Object)
};
}
}

public class DataObject2
{
public decimal Price { get; private set; }
public string PriceType { get; private set; }

public DataObject2(decimal price, string priceType)
{
Price = price;
PriceType = priceType;
}

private DataObject2()
{
}

public DataObject2 With(
Optional<decimal> price = default(Optional<decimal>),
Optional<string> priceType = default(Optional<string>)
)
{
return new DataObject2
{
Price = price.ValueOrDefault(Price),
PriceType = priceType.ValueOrDefault(PriceType)
};
}

public override string ToString()
{
return string.Format("Price:{0}, PriceType={1}", Price, PriceType ?? "null");
}
}

2 комментария:

 
GeekySpeaky: Submit Your Site!