Что нам готовит C# 7 (Часть 2. Pattern matching)
Продолжая серию статей о новшествах в С#7, акцентирую внимание на, пожалуй, главных нововведениях — Pattern matching и Record type (Примерный перевод «регистрируемые типы»). Эти функционалы дополняют друг друга, поэтому лучше рассказывать о них вместе.Начнем с Record type. Он приходит к нам из F#. По сути своей это быстрое определение класса, причем с невозможностью изменения его свойств, т.е. все его поля имеют параметр readonly, а задаются параметры в конструкторе. Описывать это достаточно долго и нудно, поэтому начнём сразу с примера кода и на примере уже все разберём. Вот пример определения record type«а: public class Cartesian (double x: X, double y: Y); Это определение некоторого класса, хранящего декартовы координаты точки. Транслироваться он должен в такой класс:
public class Cartesian { private readonly double $X; private readonly double $Y; public Cartesian (double x, double y) { this.$X = x; this.$Y = y; } public double X { get { return this.$X; } } public double Y { get { return this.$Y; } } public static bool operator is (Cartesian c, out double x, out double y) { x = c.X; y = c.Y; return true; } override public bool Equals (object obj) { if (obj.GetType () != typeof (Cartesian)) return false; var $o = obj as Cartesian; return object.Equals (X, $o.X) && object.Equals (Y, $o.Y); } override public int GetHashCode () { int $v = 1203787; $v = ($v * 28341) + X?.GetHashCode ().GetValueOrDefault (); $v = ($v * 28341) + Y?.GetHashCode ().GetValueOrDefault (); } override public string ToString () { return new System.Text.StringBuilder () .Append («Cartesian (X:») .Append (X) .Append (», Y:») .Append (Y) .Append (»)») .ToString (); } } Разберем свойства класса. В его определении мы указали два double параметра. Эти параметры транслируются в два открытых только для чтения свойства, и два поля только для чтения внутри класса. Затем создается конструктор с параметрами, указанными в определении класса. Также, создаются методы Equals, GetHashCode, ToString.Наибольший интерес представляет перегруженный оператор is. Вот он как раз уже больше относится к Pattern matching. Теперь оператор is поддерживает дополнительное сравнение, кроме обычной проверки возможности приведения к типу. Также возможен дополнительный вызов этого перегруженного оператора у класса. Начнем с того, как перегружается оператор и какие действия при этом могут совершаться. Первым параметром в операторе идет передаваемый ему объект класса, он не обязательно должен быть классом этого оператора. Затем идут возвращаемые параметры, с которыми нам нужно сравнивать или которые надо получить при выполнении оператора is. При создании класса через record type создается оператор is с передаваемым record type классом и возвращаемыми значениями этого класса, указанными в определении. Вот пример того, как сделать преобразование декартовых координат в полярные с помощью оператора is: public static class Polar { public static bool operator is (Cartesian c, out double R, out double Theta) { R = Math.Sqrt (c.X*c.X + c.Y*c.Y); Theta = Math.Atan2(c.Y, c.X); return c.X!= 0 || c.Y!= 0; } } Что мы получаем, если передать оператору объект класса Cartesian: он попытается преобразовать данные этого класса к данным класса Polar и вернет преобразованные данные.Pattern matching (или Сопоставление с образцом; хотя это название мне не очень нравится, английское определение кажется более точным), что же это такое? Пришел он к нам из таких языков как Python и F#. По сути своей это расширенный switch, который не только может сравнивать значения одного типа с константами, но и использовать приведение типов и их преобразование к необходимой структуре. И во всем этом нам поможет новый перегруженный оператор is. Начнем с новых возможностей старого оператора проверки возможности преобразования типов. Теперь вместо вот этого: var v = expr as Type; if (v!= null) { // Используем v } можно будет писать вот так: if (expr is Type v) { // используем v } Это, конечно, сократит код с приведением типов. Но вернемся к Pattern matching и узнаем, какие возможности он нам готовит. Напишем проверку приведения конкретных декартовых координат к полярным и получение радиуса: var c = Cartesian (3, 4); if (c is Polar (var R, *)) Console.WriteLine®; Итак, что здесь происходит, давайте разберемся. Берется переменная c, получается тип переменой и ищется оператор is, где первым параметром является этот тип. Далее вызывается этот оператор и, если он вернул истину, условие считается выполненным. Далее мы получаем в блоке условия локальную переменную R. Здесь нам не важен угол, и поэтому мы во второй параметр передали * — это означает игнорирование второго параметра. Еще возможно такое использование оператора: if (c is Polar (5, *)) Console.WriteLine («Радиус равен 5»); Здесь мы накладываем дополнительное условие на возвращаемое значение радиуса, и условие выполнится, только когда радиус равен 5.Основное применение новому оператору is — это, конечно, в операторе switch. Приведем пример решения алгебраических выражений с помощью pattern matching. Определим нужные нам классы с помощью record type. abstract class Expr; class X () : Expr; class Const (double Value) : Expr; class Add (Expr Left, Expr Right) : Expr; class Mult (Expr Left, Expr Right) : Expr; class Neg (Expr Value) : Expr; Для начала напишем метод взятия производной. Expr Deriv (Expr e) { switch (e) { case X (): return Const (1); case Const (*): return Const (0); case Add (var Left, var Right): return Add (Deriv (Left), Deriv (Right)); case Mult (var Left, var Right): return Add (Mult (Deriv (Left), Right), Mult (Left, Deriv (Right))); case Neg (var Value): return Neg (Deriv (Value)); } } Или упрощение выражения: Expr Simplify (Expr e) { switch (e) { case Mult (Const (0), *): return Const (0); case Mult (*, Const (0)): return Const (0); case Mult (Const (1), var x): return Simplify (x); case Mult (var x, Const (1)): return Simplify (x); case Mult (Const (var l), Const (var r)): return Const (l*r); case Add (Const (0), var x): return Simplify (x); case Add (var x, Const (0)): return Simplify (x); case Add (Const (var l), Const (var r)): return Const (l+r); case Neg (Const (var k)): return Const (-k); default: return e; } } В описаниях данного функционала я встречал в основном примеры связанные с математическими расчетами. Я буду очень рад видеть в комментариях ваши примеры, где этот функционал действительно будет полезен не в математических расчетах.Здесь можно почитать первоисточник