📜 ⬆️ ⬇️

Right-sided assignment and other unusual programming techniques in C #

In this article, such familiar and fundamental things as assigning and passing parameters to methods will be considered from a new perspective.

Probably, the proposed solutions will seem a bit strange and far-fetched at first, but their charm will be revealed a little later, when the whole picture becomes visible.

There will be many new and interesting, perhaps even useful. And after reading it, everyone will be able to decide whether he should use the described techniques in future daily practice.
')
For the cause!

image

1. Right-hand operations: assignment, declaration of variables and type conversion


There are two assignment directions: right and left.

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`, . , !


, , . !

Source: https://habr.com/ru/post/354278/


All Articles