Что нам готовит 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(R); 

Итак, что здесь происходит, давайте разберемся. Берется переменная c, получается тип переменой и ищется оператор is, где первым параметром является этот тип. Далее вызывается этот оператор и, если он вернул истину, условие считается выполненным. Далее мы получаем в блоке условия локальную переменную R. Здесь нам не важен угол, и поэтому мы во второй параметр передали * — это означает игнорирование второго параметра. Еще возможно такое использование оператора:

 if (c is Polar(5, *)) Console.WriteLine(R); 

Здесь мы накладываем дополнительное условие на возвращаемое значение радиуса, и условие выполнится, только когда радиус равен 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;   } } 

В описаниях данного функционала я встречал в основном примеры связанные с математическими расчетами. Я буду очень рад видеть в комментариях ваши примеры, где этот функционал действительно будет полезен не в математических расчетах.
Здесь можно почитать первоисточник

ссылка на оригинал статьи http://habrahabr.ru/post/257283/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *