C#での右利きの割り当てと他の珍しいプログラミングトリック

この記事では、メソッドへのパラメーターの割り当てや受け渡しなど、馴染みのある基本的なことを新しい観点から検討します。



おそらく、提案されたソリューションは、最初はやや奇妙で手に負えないように見えますが、それらの魅力は少し後になって、全体像が見えるようになったときに明らかになるでしょう。



新しくて興味深いものがたくさんあるでしょう。 そして、それを読んだ後、誰もが彼が記述された技術をさらに毎日の練習に適用するべきかどうか決定することができるでしょう。



原因に!



画像






1.右手操作:割り当て、変数宣言、型キャスト



割り当てには2つの方向があります:右と左



IModel m;
m = GetModel(); // left side assignment
GetModel().To(out m); // right side assignment

      
      





, `out` `ref` .



C# `out` `ref` , , , C# 7 !



`o.To(out var x)` , …



. , , `y = f(x)` . ( ) , , ('parentheses hell')



public void EventHandler(object sender, EventArgs args) =>
	((IModel) ((Button) sender).DataContext).Update();

// in a general case there is not possible settle priorities without parentheses
// (IModel) (Button) sender.DataContext.Update();

      
      









/* NullReferenceException instead of InvalidCastException */
public void EventHandler(object sender, EventArgs args) =>
	((sender as Button).DataContext as IModel).Update();

/* miss of InvalidCastException */
public void EventHandler(object sender, EventArgs args) =>
	((sender as Button)?.DataContext as IModel)?.Update();

/* verbose */
public void EventHandler(object sender, EventArgs args)
{
	var button = (Button) sender;
	var model = (IModel) button.DataContext;
	model.Update();
}

      
      





,



public void EventHandler(object sender, EventArgs args) =>
	sender.To<Button>().DataContext.To<IModel>().Update();
    
public static T To<T>(this object o) => (T) o;

      
      





-



public static object ChangeType(this object o, Type type) =>
	o == null || type.IsValueType || o is IConvertible ?
		Convert.ChangeType(o, type, null) :
		o;

public static T To<T>(this T o) => o;
public static T To<T>(this T o, out T x) => x = o;
public static T To<T>(this object o) => (T) ChangeType(o, typeof(T));
public static T To<T>(this object o, out T x) => x = (T) ChangeType(o, typeof(T));

      
      





: ,



sender.To(out Button b).DataContext.To(out IModel m).Update();
/* or */
sender.To(out Button _).DataContext.To(out IModel _).Update();

      
      





, C# - `to`.



((sender to Button b).DataContext to IModel m).Update();
((sender to Button _).DataContext to IModel _).Update();
/* or even */
sender to Button b.DataContext to IModel m.Update();
sender to Button _.DataContext to IModel _.Update();

      
      







2. to-with



`json`



var person = new Person
{
	Name = "Abc",
	Age = 28,
	City = new City
	{
		Name = "Minsk"
	}
};

      
      









var person = new Person();
person.Name = "Abc";
person.Age = 28;
person.City = new City();
person.City.Name = "Minsk";

      
      





, . — . -, , ,



var person = CreatePerson()
{
	Name = "Abc",
	Age = 28,
	City
	{
		Name = "Minsk"
	}
}; // cause compile errors

      
      





, - . ?



-



public static T To<T>(this T o, out T x) => x = o;
public static T With<T>(this T o, params object[] pattern) => o;

      
      









var person = new Person().To(out var p).With
(
	p.Name = "Abc",
	p.Age = 28,
	p.City = new City().To(out var c).With
	(
		c.Name = "Minsk"
	)
);

      
      









var person = CreatePerson().To(out var p)?.With
(
	p.Name = "Abc",
	p.Age = 28,
	p.City.To(out var c)?.With
	(
		c.Name = "Minsk"
	)
);

      
      





* -



, , . , `null` (`?`), , , ,



var person = CreatePerson().To(out var p)?.With
(
	...
	p.ToString().To(out var personStringView)
);

      
      





`With` :









public static T With<T>(this T o) => o;
public static T With<T, A>(this T o, A a) => o;
public static T With<T, A, B>(this T o, A a, B b) => o;
public static T With<T, A, B, C>(this T o, A a, B b, C c) => o;
		/* ... */

      
      





, `With` , ()



GetModel().To(out var m)
	.With(m.A0 = a0, ... , m.AN = an).With(m.B0 = b0, ... ,m.BM = bM).Save();

      
      





.



, . , , `put`-



public static TX Put<T, TX>(this T o, TX x) => x;
public static TX Put<T, TX>(this T o, ref TX x) => x;

      
      





, - , `With`



static AnyStruct SetDefaults(this AnyStruct s) =>
	s.With(s.Name = "DefaultName").Put(ref s);

      
      





`With`



// possible NRE
void UpdateAppTitle() => Application.Current.MainWindow.Title = title;

// currently not supported by C#, possible, will be added later
void UpdateAppTitle() =>
	Application.Current.MainWindow?.Title = title;

// classical solution
void UpdateAppTitle() {
	var window = Application.Current.MainWindow;
	if (window != null) window.Title = title;
}

void UpdateAppTitle() =>
	Application.Current.MainWindow.To(out var w)?.With(w.Title = title);

      
      





`to-with` , .



, — .



, !



GetPerson().To(out var p).With
(
	/* deconstruction-like variations */
	p.Name.To(out var name), /* right side assignment to the new variable */
	p.Name.To(out nameLocal), /* right side assignment to the declared variable */
	NameField = p.Name, /* left side assignment to the declared variable */
	NameProperty = p.Name, /* left side assignment to the property */

	/* a classical initialization-like variation */
	p.Name = "AnyName"
)

      
      





, `json` ( - ) `with` .



,



public CustomCollection GetSampleCollection() =>
	new CustomCollection().To(out var c).With(c.Name = "Sample").Merge(a, b, c, d);

/* currently not possible */
public CustomCollection GetSampleCollection() =>
	new CustomCollection { Name = "Sample" } { a, b, c, d };

      
      









public static TCollection Merge<TCollection, TElement>(
	this TCollection collection, params TElement[] items)
	where TCollection : ICollection<TElement> =>
	items.ForEach(collection.Add).Put(collection);

      
      





`check`



if (GetPerson() is Person p && p.Check
	(
		p.FirstName is "Keanu",
		p.LastName is string lastName,
		p.Age.To(out var age) > 23
	).All(true)) ...
    
if (GetPerson() is Person p && p.Check
	(
		p.FirstName.Is("Keanu"), /* check for equality */
		p.LastName.Is(out var lastName), /* check for null */
		p.City.To(out var city).Put(true), /* always true */
		p.Age.To(out var age) > 23
	).All(true)) ...

case Person p when p.Check
	(
		p.FirstName.StartWith("K"),
		p.LastName.StartWith("R"),
		p.Age.To(out var age) > 23
	).Any(true): ...

case Point p when p.Check
		(
		p.X > 9,
		p.Y > 7 && p.Y < 221
		p.Z > p.Y
		p.T > 0
	).Count(false) == 2: ...

      
      









public static bool[] Check<T>(this T o, params bool[] pattern) => pattern;

      
      







3.



put



,



use







if (GetPerson() is Person p && p.Check
	(
		...
		p.City.To(out var city).Put(true), /* always true */
		p.Age.To(out var age) > 23
	).All(true)) ...

      
      







persons.Use(out var j, 3).ForEach(p => p.FirstName = $"Name{j++}");

      
      







private static bool TestPutUseChain() =>
	int.TryParse("123", out var i).Put(i).Use(Console.WriteLine) == 123;

      
      







new



,



var words = New.Array("hello", "wonderful", "world");
var ints = New.List(1, 2, 3, 4, 5);

var item = New.Object<T>();

      
      







value propagation / group assignment



,



var (x, y, z) = 0;
(x, y, z) = 1;

var ((x, y, z), t, n) = (1, 5, "xyz");

      
      







lambda-styled type matching



`switch` -



public static double CalculateSquare(this Shape shape) =>
	shape.Match
	(
		(Line _) => 0,
		(Circle c) => Math.PI * c.Radius * c.Radius,
		(Rectangle r) => r.Width * r.Height,
		() => double.NaN
	);

      
      







Github mirror: implementation / some tests

Bitbucket mirror: implementation / some tests





`expression-bodied`, . , !





, , . !



All Articles