Комментарии 291
Оператор is однозначно говорит, о том, что объект является таким-то типом. Что говорит is var? Не понимаю зачем это нужно вообще было делать.
public static bool Is<T>(this T o, out T x) => (x = o) != null; /* or same '(x = o) is T' */
if (GetPoint().Is(out var p) Console.WriteLine("o is Any Type");
else Console.WriteLine("There is not point.");
if (GetPoint() is var p) ...
Но, увы, архитекторы языка посчитали иначе.
public Point GetPoint() => ...
По моей логике в выражении
if (GetPoint() is Point p) ...
можно применить вывод типа и получить
if (GetPoint() is var p) ...
Нона самом деле это не эквивалентная замена в C# 7.
object Foo() => rnd.Next(1) == 1 ? 123 : "456";
if (Foo(true) is string str) {}
if (Foo(true) is var bar) {} // Какого типа должна быть переменная bar?
Point GetPoint() => new Point();
var p = GetPoint();
if (p is Point pp) {} // Не имеет смысла, т.к. GetPoint() возвращает всегда только Point
if (p is var pp) {} //
if (p is Point pp) — имеет некоторый смысл, можно не декларировать переменную отдельно. Это очень удобно для однострочных методов [bodied members].
public bool IsValid() => GetPoint() is Point p ? Validate(p.X, p.Y) : false;
Иначе пришлось бы писать только так
public bool IsValid()
{
var p = GetPoint();
return p is Point ? Validate(p.X, p.Y) : false;
// 'p is Point' в нашем случае можно равноценно заменить на 'p != null'
}
GetPoint().Is(out var p)
это вообще может скомпилится? Компилятор должен вывести типы для Is и для out var p, но он не сможет это сделать.
GetPoint() is var p
— на мой взгляд тоже не должно компилится по тем же причинам, что не компилится Point p = GetPoint(); if(p is var) {}
В C# 7 добавлена новая фишка, вместо
int i;
var i = 0;
int.TryParse("123", out i)
можно теперь писать так
int.TryParse("123", out int i)
int.TryParse("123", out var i)
Поэтому следующее выражение компилируется в C# 7
GetPoint().Is(out var p)
Однако следующие выражения уже относятся к другой новой фишке — pattern-matching, но и они успешно компилируются
if (GetPoint() is Point p)
if (GetPoint() is var p)
Для меня (хотя я и не знаток c#) решение авторов языка довольно очевидно: var — вывод типа. Т.е. в данном случае тип, который может быть возвращён GetPoint — как если бы мы написали var p = GetPoint(). Если GetPoint может вернуть null — значит, p может быть null. Или мы выстрелим себе в ногу.
var p = GetPoint(); // только вывод типа
if (GetPoint() is var p) ...
// условно эквивалентно
if (GetPoint() is null or Point p) ...
if (GetPoint() is any p) ...
if (GetPoint() is null or Point p) ...
if (GetPoint() is null or var p) ...
Так работает паттерн-матчинг во всех языках. var p — паттерн, который матчит любое значение, аналог «х» в foo x =… в хаскеле, например. Очевидно, что этот паттерн матчится _всегда_. Сделать другое поведение — было бы очень неожиданным для любого человека, знакомого с тем, как работает паттерн-матчинг.
Если вам не нравится синтаксис с var, то как вы предлагаете матчить произвольный паттерн?
Тем более такой синтаксис попал в нагрузку с логикой switch:
switch(SomeMethod())
{
case int i: return $"int:{i}";
case double d: return $"double: {d}";
case var v: return "Any other";
}
В данном случае вводится переменная внутри выражения.
Any и All возможны, но плохо передают суть происходящего, единственный нормальный варинат — это let, но такого ключевого слова в шарпе нету, лучше взять уже имеющееся (var), тем более, что его семантика внутри паттерна полностью совпадает с семантикой вне паттерна.
public static bool IsNull(this object o) => o is null;
public static bool Is(this T o) => o != null; /* or same 'o is T' */
как объект на котором вызывают метод может быть null?
В C# 7 незаметным образом, на мой взгляд, просочилась подмена значения оператора 'var', теперь это может значить что-то ещё…
Не таким уж и незаметным: Pattern Matching, раздел "var
declarations in case
expressions". Единственное, что оттуда не очевидно, это то, что это точно так же применимо к оператору is
.
Модификатор — это и есть "контекст использования", так что ничего не меняется. Новое ключевое слово, конечно, круто, но тогда были бы неконсистентные декларации (да и новые ключевые слова — это breaking change).
Конечно, если добавлять новые ключевые слова уже сейчас, после релиза, то да — это breaking change. Но до момента релиза это ещё не breaking change :)
На мой взгляд, на консистентность не должно было бы повлиять введение новых ключевых слов для определённых контекстов.
Да ну:
Point p;
Circle c;
var v = new Point();
//...
case Point p:
case Circle c:
case ? v:
Конечно, если добавлять новые ключевые слова уже сейчас, после релиза, то да — это breaking change.
Это когда угодно breaking change.
Breaking change — это то, что ломает логику программы при компиляции на следующей весрсии языка (нарушение обратной совместимости). Можно добавить хоть десяток новых ключевых слов, но если старый код компилируется одинаково успешно, как на новой, так и на старой версиях языка, то никакого breaking change в классическом понимании не произошло. Просто расширился синтаксис и функционал, добавились новые ключевые слова и допустимые выражения с ними.
Breaking change — это то, что ломает логику программы при компиляции на следующей весрсии языка (нарушение обратной совместимости).
Ну да. Добавление нового ключевого слова именно таким и будет. Вот допустим, решили вы добавить ключевое слово any
вместо var
. Чтобы, значит, было is any x
. Представьте на мгновение, что кто-то из разработчиков объявил тип, названный any
. Что будет, когда он перекомпилирует свой код под новой версией языка? Правильно, смена поведения (даже не ошибка компиляции).
Вот это как раз была бы адская неоднозначность.
Более того, ради интереса сейчас проверил, можно ли объявить класс с именем 'var'. Можно! Всё компилируется, вот только 'var' в обычном значении перестаёт работать, поскольку воспринимается как тип, а ведь когда-то же и 'var' не было в C#.
Почему же неоднозначность? Ключевое слово 'any' без 'x' употребляться не может само по себе.
Потому что стоит забыть переменную и поведение радикально меняется.
ведь когда-то же и 'var' не было в C#.
Ну так это и был breaking change.
Конечно, для разработчика нововведение может выглядеть ужасно непредсказуемо и даже ломать его логиченые и интуитивные мыслительные шаблоны, как произошло у меня с 'var', но, скорее, это можно назвать pattern breaking. :)
По крайней мере, я не могу придумать пример, где бы это ещё могло проявиться.
Если я ничего не путаю, все другие места с этим ключевым словом все равно потребуют эскейпинга.
Это не unexpected, а скорее «невыученный» behavior. В любом языке их полно, маленькие и большие нюансы, любой из которых unexpected для новичка. C нетерпением жду перехода нашего отдела на VS 2017 и свежих граблей!
P.S.: is any — интересная идея, но, как вы понимаете, запоздалая, т.к. is var уже используют и вряд ли кто-то обрадуется несовместимости нового C#.
if (MyMethod() is var x) // x is not null
if (MyMethod() is var? x) // x can be null
if (GetPoint() is Point p) // всегда true
if (GetPoint() is Point? p) // true/false
if (GetPoint() is var p) // true/false, если метод возвращает Point?, только true, если Point
if (GetPoint() is null or Point p) // всегда true или неприминимо
if (GetPoint() is null or Point? p) // всегда true
if (GetPoint() is any p) // всегда true
Основные тезисы изложены в следующей цитате:
I already provided the arguments as to why this was done. I explained in several threads that there are two completely valid ways that people intuit what 'var' means.
One intuition (the one you seem to have) is that 'var' 'takes the place of a type'. i.e. if you previous had a type written somewhere, you can replace it with 'var' and the code will have the same meaning. (it's worth pointing out that this intuition is wrong in some cases).
Another intuition (that was used when designing pattern matching) is that 'var' is used to simply declare a variable, where the type of the variable is inferred from context.
Note that up to now, neither of those intuitions prevented a null value from being used with 'var'. After all, you've been able to write this since C# 3.0:
var v = GetValue(); //... string GetValue() { return null; }
Look! We used 'var' to make a variable called 'v' and it will have a 'null' it in at runtime. And that's all by design and how things have worked for years.
— So the question then becomes «if we use 'var v' as a pattern, what is the appropriate meaning for it». Given the above two intuitions there are two ways it could have gone. We could have gone with the intuition that 'var' is simply a substitute for a type. As such x is var v should mean the same as x is T v, and thus would disallow null. OR we could go with the intuition «var just declares a variable and allows anything in it». (Which is what we went with).
Now you may ask: why go with the second intuition? I far prefer the first intuition!
There are several reasons for this, but i'll go with the most significant one:
We want recursive pattern matching to work, and we want a way for people to be able to decompose an object and unconditionally put decomposed subvalues into a variable regardless of what their value is. The last part is important. We know people will want a way to decompose a value into subparts and have that always succeed even when those subparts have the 'null' value. What's the easiest and most intuitive way to say «this variable is strongly typed and can hold any value»? «var v». So we picked the second intuition for «var v» and are using that.
— Now: i totally get that you don't want us to have made that choice. That's totally fine. You are welcome to have that opinion. To a certain extent i totally understand and appreciate that opinion. It is confusing. It is strange to have is var v not the same as is S v. I wish we'd found a more suitable choice that didn't have this sort of baked-in historical ambiguity.
But you know what? That ship has SAILED. It's happened. It's over. And unless it was literally an absolutely horrid decision (which it isn't), it's not going to change. And, while the discussion of a new language is interesting and all… it's not going to happen for a long time either. C# is still healthy, widely used, and much more full of good stuff than bad stuff. Maybe in 10 years that equation will change and we'll need something new. But that time is not now, and this specific language choice is not what you want alienate everyone over.
In other words: it's totally fine to make your opinion known. It's fine to debate and try to convince. But at a certain point, just continuously not being able to let something go (especially something as small as this) just doesn't serve any purpose and only makes people unwilling to converse with you in the future.
Что касается паттерна `a is {} b`, то это не новый синтаксис, а частный случай рекурсивного паттерна `a is {Foo = 10, Bar = «hello»} b`, который матчит a на b, если эти поля совпадают. Никаких компенсаций синтаксиса тут нет, это просто частный случай фичи, которую так или иначе добавят в язык. `a is var b` вообще для меня довольно бесполезный паттерн альтернативного объявления переменной, но то, что он должен всегда быть успешным для меня не вызывает сомнений. Почему вы хотите ВВЕСТИ в язык unexpected behaviour вместо уже имеющегося очевидного?
Он будет нужен в подпаттернах.
Почему вы хотите ВВЕСТИ в язык unexpected behaviour вместо уже имеющегося очевидного?
Ознакомьтесь детальнее с альтернативным предложением и вы увидете, что никакого unexpected behaviour оно не несёт, а наоборот выглядит гораздо более прозрачно.
Прежде чем принять решение, хорошо подумайте, поскольку тут есть достаточно веские «за» и «против». Не помешает и более подробное изучение вопроса и соответствующих дискусий.
Во-вторых, не большинства, а большого числа людей.
Может, для вас он очевиден, но для большого числа других людей, увы, нет.
Вам нужны примеры? Пожалуйста, не я один такой.
Вот вопрос про swich с большим рейтингом
Куча предложений об одном и том же:
github.com/dotnet/csharplang/issues/792
github.com/dotnet/csharplang/issues/1192
github.com/dotnet/csharplang/issues/306
github.com/dotnet/csharplang/issues/1078
github.com/dotnet/csharplang/issues/1054
github.com/dotnet/csharplang/issues/1203
Во-вторых кучи предложений нет. Вопрос на stackoverflow я еще могу вас засчитать, хотя рейтинг вопроса не показывает, насколько это сложный концепт, и что поведение языка нужно как-то менять. Ну например вот. Значит ли это, что каст энума реализован плохо и неинтуитивно?
Насчет кучи обсуждений — они вообще про другое, про трактовку `var` в языке с ненулевыми ссылочными типами.
В-третьих вам привели пример:
var point = new Point<int?, int?>(0, null);
WriteLine(point switch {
(0, 0) => "Both values zero.",
(0, var second) => $"First value is 0 in (0, {second}).",
(var first, 0) => $"Second value is 0 in ({first}, 0).",
_ => "Both nonzero."});
Ваше предложение просто молча разломает этот код, который отлично работал раньше.
И да, даже вопрос на stackoverflow не про то, а про то, зачем default версия если все они покрыты. Но можно то же самое задать про вопрос с true false default свитчем, как правильно заметили в первом же ответе.
Насчет кучи обсуждений — они вообще про другое, про трактовку `var` в языке с ненулевыми ссылочными типами.
Вот и я про то же — про трактовку 'var'. Моё мнение такое, 'var' следует использовать толко для вывода типов без навешивания дополнительной контекстнозависимой логики. Тогда это будет ясно, чисто, красиво.
Данный код ещё не в релизе, поэтому его можно ломать. Кстати, в процессе обсуждения образовалось и новое предложение по двустороннему матчингу, поэтому можете высказать и свои мысли на этот счёт.
Почему-то считается, что PM появился только в C# 7, но, на мой взгляд, отдельные его элементы в зачаточном виде присутствовали уже в более ранних версиях языка. Я говорю про инициализацию свойств в '{ X=1, Y=2}', а также про операторы 'is', 'as' и приведение типа. Новые фичи PM можно весьма гармонично вписать в прежний контекст.
Почему-то считается, что PM появился только в C# 7, но, на мой взгляд, отдельные его элементы в зачаточном виде присутствовали уже в более ранних версиях языка.
Те примеры, которые вы приводите — это не паттерн-матчинг.
Я придерживаюсь того же взгляда на паттерн-матчинг, который используется в функциональных языках. Это вполне конкретная конструкция и прием.
Но я придерживаюсь той позиции, что PM в C# может быть ещё лучше, чем в других языках.
Может. На его определение это никак не повлияет.
Взгляните на следующее предложение по улучшению синтаксиса.
Это предложение не имеет отношения к паттерн-матчингу. Хуже того, оно порождает побочные эффекты, которых лучше избегать.
Изменение значения переменной из внешнего скоупа. Не надо так.
string activeValue = default(string);
...
GetModel() { AnyProp0 to activeValue }.AttachModel();
Здесь предложено одно из решений проблемы, потенциально возможно и другое — усложнение логики компилятора (контроль null-ветвлений кода).
Однако с другой стороны — объявления переменных в классах также эквивалентны записи
string activeValue = default(string);
поэтому такое решение тоже возможно.
При чем тут вообще null
-ветвления и объявления членов классов?
//var p = GetPoint()? { X to var x, Y to var y };
var p = GetPoint()?.Unwrap(out var x, out var y);
if (p == null) WriteLine("It is null");
else WriteLine($"X={x}; Y={y}");
Сейчас такой код не компилируется с ошибками
[CS0165] Use of unassigned local variable 'x'
[CS0165] Use of unassigned local variable 'y'
Хотя если 'p != null', обе переменных будут успешно проинициализированны и можно безопасно их использовать. То есть в теории здесь можно добавить контроль ветвлений для решения проблемы компиляции.
Либо же достаточно проинициализировать переменные дефолтными значениями. Если программисту будет важно, откуда пришли эти значения, то он сможет разобраться, используя значение переменной 'p'. Ведь если мы объявляем переменную в классе без явной инициализации, то она инициализируется дефолтным значением и компилятор не ругается, что мы её не проинициализировали чем-то определённым.
class AnyClass
{
private int _anyVariable0;
private string _name;
public Print() => Console.WriteLine(_name);
}
Вы вообще не поняли, про что я вам говорю. Меня не устраивает, что код
string activeValue = default(string);
...
GetModel() { AnyProp0 to activeValue }.AttachModel();
Изменяет значение переменной activeValue
. Всё.
Либо же достаточно проинициализировать переменные дефолтными значениями.
Это нарушит текущее поведение с out
.
Ведь если мы объявляем переменную в классе без явной инициализации, то она инициализируется дефолтным значением
Есть маленькая разница между свойствами класса и переменными метода. Куча и стек.
Есть маленькая разница между свойствами класса и переменными метода. Куча и стек.
Да, разница есть, но она не столь существенна с практической точки зрения, от лишнего присваивания производительность метода не просядет.
Также не идёт речи про изменение поведения 'out', идёт речь про оператор 'to', который инициализирует переменную дефолтным значением, если для неё оказалось недоступным другое.
Вас это не устраивает, но меня вполне. Тот синтаксис, который можно будет получить, благодаря этому допущению, выглядит для меня в разы более привлекательным, чем лишнее присваивание.
Да, разница есть, но она не столь существенна с практической точки зрения,
Вы это серьезно?
от лишнего присваивания производительность метода не просядет.
Не только присваивания, но и выделения памяти. Вы думаете, стек — он бесконечный?
Также не идёт речи про изменение поведения 'out', идёт речь про оператор 'to', который инициализирует переменную дефолтным значением.
Я уже объяснил, чем лично меня не устраивает этот оператор.
Тот синтаксис, который можно будет получить, благодаря этому допущению, выглядит для меня в разы более привлекательным, чем лишнее присваивание.
Я и говорю: вам плевать на побочные эффекты.
идёт речь про оператор 'to', который инициализирует переменную дефолтным значением
var p = GetPoint()? { X to var x, Y to var y };
Оператор to
не может ничего проинициализировать, если он не выполнится, а если GetPoint
вернет null
, именно это и произойдет.
В данном случае контроль ветвлений как раз и говорит: есть ветка, в которой переменная не будет инициализирована. А NRE можно получить только для reference-типов (и это все равно инициализация).
//var p = GetPoint() { X to var x, Y to var y);
var p = GetPoint().Unwrap(out var x, out var y);
if (p == null) WriteLine("It is null");
else WriteLine($"X={x}; Y={y}");
Вообще-то метод 'Unwrap' вызовется, если
Я говорил о вполне конкретном коде:
var p = GetPoint()? { X to var x, Y to var y };
А если вернет null
— не произойдет. О чем и речь.
В этом коде нет такой ветки.
int? x,y;
var p = GetPoint();
if (p != null)
p.Unwrap(out x, out y);
if (p == null) WriteLine("It is null");
else WriteLine($"X={x}; Y={y}");
new Point() { X = GetPont()?.Unwrap(out var x, out var y) }
new Point() { X = GetPont()? { X to var x, Y to var y) }
Поэтому дополнительная инициализация переменной дефолтным значением с помощью 'to'+'var' не является чем-то из рядя вон выходящим.
Поэтому дополнительная инициализация переменной дефолтным значением с помощью 'to'+'var' не является чем-то из рядя вон выходящим.
Эм. Инициализация переменной оператором, который не выполняется — это не что-то из ряда вон выходящее? Ну-ну. Спасибо, нет.
IModel GetModel() => null;
GetModel()?.To(out var m);
m = new AnyModel();
… я еще понимаю, вы бы конвееры предложили, вот чего правда не хватает.
но про такую функцию в языках программирования не слышал
… и эти люди рассказывают нам про паттерн-матчинг.
На C# я пишу довольно много и доводилось делать непростые вещи… Если вам очень интересно, то можете полистать примеры кода.
Моя главные ориентиры в программировании — красота, лаконичность и простота. Основные мерила — обобщённость и количество строк. Из двух одинаково обобщённых решений для меня наиболее привлекательно то, в котором меньше строк.
Предлагаемая стандартная реализация PM будет мешать мне писать bodied-методы и решать некоторые задачи меньшим числом строк кода, поэтому у меня есть большое желание её переработать для большей лаконичности.
Конечно, прекрасно понимаю, что есть некоторые breaking changes, поэтому шансы что-то изменить призрачны, но, по крайней мере, я пробую свои мысли кому-то донести.
Уж не обобщайте так, я не рассказываю про тот PM, каким вы его привыкли видеть.
А зачем вы рассказываете про что-то свое, а потом называете словами, которыми другие люди называют совсем другое?
Моя главные ориентиры в программировании — красота, лаконичность и простота.
Два из трех субъективны.
Из двух одинаково обобщённых решений для меня наиболее привлекательно то, в котором меньше строк.
Я так и понял, на читаемость вам наплевать.
Предлагаемая стандартная реализация PM будет мешать мне писать bodied-методы и решать некоторые задачи меньшим числом строк кода,
Она не будет мешать вам делать ничего, что вы уже можете делать сейчас.
Она не будет мешать вам делать ничего, что вы уже можете делать сейчас.
Увы, вероятно, она закроет для меня такой синтаксис
GetModel() { AnyProp0 = "abc" }.AttachModel();
Он сейчас доступен вам?
(btw, паттерн .WithX
прекрасно решает такие проблемы)
Многие проблемы прекрасно решаются и без PM с помощью extension methods. Это же не повод отменять PM, но и не повод не делать такую фичу. Так почему бы не скомбинировать оба подхода, если это возможно сделать?
Не доступен, но очень хотелось бы.
Значит, мое утверждение "Она не будет мешать вам делать ничего, что вы уже можете делать сейчас" верно.
Многие проблемы прекрасно решаются и без PM с помощью extension methods.
… вот, например, стандартный switch...case...
для разных типов с условиями. Не покажете, как?
Это же не повод отменять PM, но и не повод не делать такую фичу.
Повод "не делать такую фичу" — стремление избежать избыточных побочных эффектов. Для меня, по крайней мере.
Так почему бы не скомбинировать оба подхода, если это возможно сделать?
Потому что невозможно. То, что вы показываете — не pattern matching.
Пока что для меня ваш паттерн матчинг ради паттерн матчинга. Мой «не паттерн-матчинг» — синтаксический сахар ради лаконичности кода.
Мне пока никто не привёл примера кода, который невозможно было бы переписать методами-расширениями.
Только что же:
switch (c)
{
case int i: ...;
case string s: ...;
case Toogle t when t.Enabled: ...;
case Toogle t: ...;
case null: ...;
}
Это можно переписать, но вы потеряете в читаемости и функциональности.
(и это я не говорю про типовое для других языков
match v with
| Some x -> x*2
| None -> None
)
Пока что для меня ваш паттерн матчинг ради паттерн матчинга.
Ну да, они ради паттерн-матчинга. Который, собственно, нужен с завидной регулярностью.
Суть в другом — в расширении значения 'var' и 'is var', без этого можно обойтись.
В вашем примере вообще ничего переписывать не нужно
Стоп-стоп. Вы говорили, что "Многие проблемы прекрасно решаются и без PM с помощью extension methods". Ну вот, решайте.
Однозначно, это можно оставить как есть.
Без паттерн-матчинга? Невозможно.
Суть в другом — в расширении значения 'var' и 'is var', без этого можно обойтись.
Только ценой ввода нового ключевого слова.
Да. Лучше ввести новое ключевое слово, чем расширять смысл уже имеющихся 'var', 'is'.
Как же невозможно? ) Старый добрый if-else да 'is', 'as' в помощь или даже elvis оператор '? :'.
Во-первых, это не экстеншн-методы, как вы обещали. Во-вторых, это как раз с потерей читаемости, о чем я сразу и сказал.
Да. Лучше ввести новое ключевое слово, чем расширять смысл уже имеющихся 'var', 'is'.
Кому лучше-то? Вам лучше, другим, как выяснилось, нет.
Вот и я про то же — про трактовку 'var'. Моё мнение такое, 'var' следует использовать толко для вывода типов без навешивания дополнительной контекстнозависимой логики. Тогда это будет ясно, чисто, красиво.
Так это вы навешиваете логику. В шарпе всегда можно было писать:
Func<string> getNull = () => null;
var foo = getNull()
Тут var переменная null. Вы почему-то предлагает контекст, котором var никогда не null, нарушая существующую интуицию.
Данный код ещё не в релизе, поэтому его можно ломать. Кстати, в процессе обсуждения образовалось и новое предложение по двустороннему матчингу, поэтому можете высказать и свои мысли на этот счёт.
Данный код в релизе, и его ломать нельзя. Вот например выдержка из релизной версии моего проекта:
Зачем вы его сломать хотите?
Почему-то считается, что PM появился только в C# 7, но, на мой взгляд, отдельные его элементы в зачаточном виде присутствовали уже в более ранних версиях языка. Я говорю про инициализацию свойств в '{ X=1, Y=2}', а также про операторы 'is', 'as' и приведение типа. Новые фичи PM можно весьма гармонично вписать в прежний контекст.
Если у вас инициализатор свойств это паттерн матчинг — то вам просто нужно изучить тему. У вас просто недостаточно квалификации, чтобы иметь мнение на эту тему. Ваша цитата:
- That expression is recursive. But it contains no patterns, recursive or otherwise.
- no, I use C# for long time only and consider all new features from C# intuition.
Поизучайте, как работает матчинг. Как вы хотите — работать не будет. Во-первых потому что это неинтуитивно — во всех остальных языках паттерн var foo матчится ВСЕГДА. В итоге в вашем варианте для таких случаев придется костылить отдельные какие-то вещи a is alwaysmatch b
что-нибудь в таком духе. Но тут нужно понимать, что это очень частый кейз, и за набор кучи букв люди спасибо не скажут.
Во-вторых я привел пример из продакшна, который ваши изменения сломают, будучи принятыми майкрософтом. А т.к. ломающие изменения для них это большое no-no, то ничего этого не произойдет. Поэтому обсуждать эти изменения можно только в контексте "конечно жаль, что сейчас это невозможно сделать, но вот как хорошо было бы, если ...". Но даже в таком варианте получается какая-то фигня, как вам уже показали.
Паттерн матчинг должен иметь возоможность матчить значения на переменные, и делать это всегда успешно. В вашем случае это не работает с null-ами. В итоге ваше предложение не является паттерн матчингом в текущем виде вообще.
Func<string> getNull = () => null;
var foo = getNull();
foo = foo ?? "abc";
Не понимаю, что здесь нелогичного? Функция возвращает nullable string, компилятор выводит для переменной foo соответствующий тип. Эквивалентно
Func<string> getNull = () => null;
string foo = getNull();
foo = foo ?? "abc";
'var' используется в польностью классическом понимании.
Вы мыслите шаблонно, PM так работает в других языках. Но что если предположить, что в C# может быть ещё лучше? Просто изучите новое предложение по улучшению синтаксиса языка. Вы можете найти в нём противоречия?
Хочу вас обрадовать. Ваш релизный код не сломается в контексте C# 8, он может перестать работать только для nullable value types (bool?, int?, etc.).
Мы сейчас nullable не рассматриваем, ибо теоретически его вообще в язык могут и не добавить. Мы говорим про текущий работающий код. Ваше предложение заставит срабатывать третью ветку вместо второй, если var вдруг перестанет матчит нуллы.
Вы мыслите шаблонно, PM так работает в других языках.
Сначала вы говорите "это нарушает мою интуицию C#-разработчика, а потом нарушаете эту самую интуицию и говорите, что человек "мыслит шаблонно". Вы уж определитесь.
Хочу вас обрадовать. Ваш релизный код не сломается в контексте C# 8, он может перестать работать только для nullable value types (bool?, int?, etc.).
В любом случае, он не должен ломаться ни для каких случаев, даже самых редких.
Просто изучите новое предложение по улучшению синтаксиса языка. Вы можете найти в нём противоречия?
Полностью согласен с Яковом
Это ваше право так считать, что код не должен ломаться. Однако лично я согласен на некоторые breaking changes в мажорных релизах, которые помогут мне сделать язык более красивым и выразительным.
Однако лично я согласен на некоторые breaking changes в мажорных релизах, которые помогут мне сделать язык более красивым и выразительным.
Круто. Вы, конечно, согласны лично пойти и починить все ошибки, возникшие в результате этих breaking changes?
Это ваше право так считать, что код не должен ломаться. Однако лично я согласен на некоторые breaking changes в мажорных релизах, которые помогут мне сделать язык более красивым и выразительным.
Может вы и согласны, только вы же предлагаете это решение другим? Ну то есть это то, что называется «продать идею». И ваша ЦА совсем иначе смотрит на проблемы сломать существующий код.
синтаксис, который гармонично вписывается и развивает язык, открывая путь новым функциям.
В вашем предложении очень много костылей, которые язык не поворачивается назвать «гармоничным синтаксисом, открывающим путь новым функциям». Я согласен, что написать дельное предложение трудно, я сам в своем пропозале накостылил очень много всего нехорошего, и скорее всего сам бы не захотел иметь такое в языке, но я хотя бы согласен с тем, что проблема на моей стороне.
На моей взгляд, вы очень не критично относитесь к своему предложению. Попробуйте месяц погулять, поработать с C# 7.0, поиспользовать паттерн матчинг как в нем, так и в других языках, в том же Rust например. А потом вернуться и свежим взглядом посмотреть, что же вы такого предложили.
Уважаемый Makeman, скопирую сюда свой комментарий из обсуждения на гитхабе.
var x
и Type x
— это не один и тот же паттерн, они просто похожи. Type
(x тут опционален) нужен в первую очередь для проверки типа, а уже во вторую очередь для присвоения сматченного значения переменной. var x
(без переменной тут не обойтись) нужен в первую очередь для присвоения любого значения переменной и используется как подпаттерн для деконструкции. Он не матчит принципиально, а получает значение от надпаттерна.
Да, если использовать его как самостоятельный паттерн, то получается, что он просто матчит значение целиком и пишет его в переменную безо всякой проверки. Но если добавить тут в логику проверку на null
, то сломается его основная функция — записывать в переменную кусок разобранного паттерна. Ну и вообще, я же могу написать var x = default(string);
? var
тут не пытается проверить, null
я записываю или нет.
И вообще, не надо использовать паттерн-матчинг для проверки на null
. Во-первых, можно написать if (x != null)
, и в C#8 x
внутри консеквента станет гарантированно непустым значением. Если нужно обязательно использовать switch
, то там есть паттерн null
, можно написать
switch (x) {
case is null: ...
case _: ... //тут можно использовать x
}
Я надеюсь, что в C#8 внутри case _:
значение x
также станет непустым.
Какое расширение вы тут видите?
var x = default(string);
Вы внезапно навешиваете на var еще и смысл "проверяет, что не null". Вот как раз в случае выше var — это просто вывод типа. А у вас еще и проверка на null. Прекрасно, что у вас такое видение, но нужно уметь и других слушать, а не только затыкать уши "лалала я вас не слышу лалала вас не существует".
Вы меня слышите? На 'null' проверяет оператор 'is', а не 'var'.
a is var b
— b может быть null?
Ээээ… вы вообще в сгенеренный код смотреть пробовали? if (a is var b) return;
превращается в if (true) return;
(это в дебаге, в релизе, подозреваю, вообще весь код выкнется).
А зачем проверять, что b
может быть null
? Это совершенно избыточный код, даже в идеальном случае.
if (GetModel() is IModel m) WriteLine($"It is model {m}");
else WriteLine("It is null or not model");
if (GetModel() is var m) WriteLine($"It is model {m}");
else WriteLine("It is null");
Если вам важна проверка, напишите ее: GetModel() is var m && m != null
(в case
, соответственно, будет case var m when m != null
).
Это консистентно с поведением var
в других местах.
Это каком конкретно?
github.com/dotnet/csharplang/issues/1224
и отчасти тут (детали в самой дискуссии)
github.com/dotnet/csharplang/issues/1196
Не, не консистентно. var x
— это декларация переменной. Если она будет жестко non-nulllable, это не будет совпадать с поведением var x
в out var x
, и, тем самым, неконсистентно.
Да, я знаю, что is Type x
неконсистентно с out Type x
, но (а) вторая форма не используется и (б) is Type x
консистентно с is Type
.
… надо сказать, я впечатлен терпением LDT.
if (GetModel() is {} m) WriteLine($"It is model {m}");
else WriteLine("It is null");
Тоже костылек?
Стоит подумать, почему так получилось. Обычно рассуждения в стиле «они все идиоты, сделали такую хрень» означают то, что проблема на другой стороне.
Вот вполне рабочая модель подобного подхода
github.com/dotnet/csharplang/issues/1196#issuecomment-355054947
I'm confused. I don't see any pattern matching in the code sample you just posted.
… потому что это не способ объявления переменной?
if (GetModel() is object m) WriteLine($"It is model {m}");
else WriteLine("It is null");
Но мне нужен сам тип, чтобы, например, обратиться к переменной 'm.Name'
if (GetModel() is var m) WriteLine($"It is model {m.Name}");
else WriteLine("It is null");
Так напишите var x = GetModel(), а потом в паттерне спокойно проверяйте на is null.
Вы не можете объявить переменную {} m
в произвольном скоупе.
Вам уже ответили, что {} m
это частный случай общего паттерна, который как раз дает оператор матча на не нулл бесплатно. И да, в языке ДОЛЖЕН быть оператор матчинга во всех случаях, даже если это выглядит как "еще один способ декларации переменной". Я тоже был одно время против неё, но LDM привели убедительные доводы, почему так должно быть. Просто игнорируя то, что вам говорят и продолжая твердить одно и то же вы ничего не добьетесь.
Паттерн `var x` ничего не проверяет, он просто создаёт x, пишет туда то, что получил и возвращает true.
Дисконнект есть не между семантикой паттерна `var` и объявления переменной с выводом типа, а между паттерном `Type x`, который хочет получить на вход ненулевое значение и объявлением переменной `Type x`, которой может быть присвоен null.
Но это сгладится введением Nullable Reference Types в C#8, где `Type x = null;` будет выдавать предупреждение.
Не забывайте включать markdown (я тоже забываю :) ).
Никаких особых неконсистентностей нет, a is Type b
все так же матчит только ненулл. То что a is var b
матчит нулл запомнить проще, чем весь тот предлагаемый ад. Плюс у нас есть a is {} b
, который матчит не нулл точно так же, если лень писать полтора символа идентификатора.
Так тут и есть неконсистентность. null является значением типа Type (по факту, в системе типов шарпа), но при этом при проверке null is Type = false. Type при объявлении переменной — это nullable type, а Type в is — nonnullable Type. В зависимости от контекста это разные Type, вот в чем проблема. Чтобы все было консистентно, надо либо чтобы референс типы были nonnullable, либо чтобы null is Type = true, то есть null должно входить в тип либо не входить в него в обоих случаях. Либо надо вводить понятие, аналогичное конструктору, и привязывать семантику Type внутри паттерна к нему. Как бы внутри паттерна это не тип, а «конструктор» (что-то подобное) типа, потому логично, что они могут вести себя поразному.
bitbucket.org/Makeloft/ace/issues/1/research-of-pattern-matching
CC: lair PsyHaSTe orthoxerox
Не вижу никакого "разбора", снова набор каких-то случайных утверждений.
Если же вы не улавливаете взаимосвязи, то, к сожалению, ничем не могу помочь.
А зачем мне это, особенно с тем учетом, что вы приводите ссылку на хабре, но даже не утруждаете себя статьей для обсуждения?
Спасибо, нет.
Если устали обсуждать, то не надо было ее заново поднимать, публикуя ссылку.
Выгляди так, словно ваша самоцель — обсуждения на хабре. Моя цель — поиск истины, математической красоты и рабочих решений, а обсуждения — только малая часть этого пути.
Моя цель — поиск истины, математической красоты и рабочих решений
… а на мнение других людей вам более-менее все равно. Ну да, ну да.
К сожалению, убедительных аргументов в пользу того, что 'is var' и 'case var' для чего-то должны возвращать всегда только 'true' мне найти не удалось, хотя обратных доводов могу привести немало.
Вовсе не утверждаю, что теперь нужно вносить breaking changes, меня больше интересует, как мог бы выглядеть язык в идеальном случае. Ну, и то, что можно теперь использовать методы расширения 'Is' с чистой и предсказуемой имплементацией в случае необходимости. Кстати, для подхода с методом 'Is' можно также получить ряд любопытных обобщений, которых нет сейчас в языке.
Вовсе не утверждаю, что теперь нужно вносить breaking changes, меня больше интересует, как мог бы выглядеть язык в идеальном случае.
Никак. Не существует идеального случая для языка общего назначения.
Ну, и то, что можно теперь использовать методы расширения 'Is' с чистой и предсказуемой имплементацией в случае необходимости.
… в то время, как для большей части (а в моей личной практике — для всех) случаев достаточно использовать оператор is
. Ну да, ну да.
… в то время, как для большей части (а в моей личной практике — для всех) случаев достаточно использовать оператор is. Ну да, ну да.
Как же вы отличаете те случаи, где можно было бы ещё применить оператор 'is', если даже не знаете про какие конкретно обобщения я говорю?
Коечно, до того, как ввели, например, pattern-matching, большинству разработчиков хватало и базовых возможностей 'is'.
Как же вы отличаете те случаи, где можно было бы ещё применить оператор 'is',
Я беру те сценарии, для которых применим ваш метод-расширение, и рассматриваю их.
referencesource.microsoft.com/#mscorlib/system/collections/generic/equalitycomparer.cs
… а зачем вообще писать smth is 0
, когда можно написать smth == 0
?
Более того, вы утверждаете, что ваше решение "более оптимально", но по какому критерию оптимальности? Даже если рассмотреть банальную производительность — у вас есть конкретные бенчмарки, которые бы показывали, что две операции боксинга дороже, чем три статических вызова и один виртуальный?
И да, интуитивно я предполагаю, что компилятор умный и развернёт 'i is 0' в 'i == 0', но он поступает иначе. Конечно, 20% не такой уж большой выигрыш в производительности, но уже ощутимый, и, стоит отметить, методы-расширения поддерживают не только константные выражения.
Кроме того, следующая перегрузка позволяет указывать 'fallbackValue', что позволяет писать более гибкую логику
public static TX Put<T, TX>(this T o, TX x) => x;
public static bool Is<T>(this object o,
out T x, T fallbackValue = default(T)) =>
(x = (o is T).To(out var b) ? (T) o : fallbackValue).Put(b);
if (model.Is(out AnyModel m, AnyModel.Default))
Console.WriteLine($"Custom AnyModel {m}");
else Console.WriteLine($"Default AnyModel {m}");
Поэтому возникает закономерный вопрос, раз методы-расширения более обобщённые и не уступают (а иногда и выигрывают) по производительнотсти, то зачем мне использовать встроенный, но весьма ограниченный синтаксический сахар?
Раз уж язык позволяет стандартными средствами написать 'i is 0', то почему бы и нет?
Потому что боксинг. И зачем, спрашивается? Не говоря о том, что я вообще не очень понимаю, зачем писать is константа
.
Конечно, 20% не такой уж большой выигрыш в производительности, но уже ощутимый
Это пишет человек, которому только что было все равно на четырехкратное изменение?
Кроме того, следующая перегрузка позволяет указывать 'fallbackValue', что позволяет писать более гибкую логику
(x as T) ?? fallback
. Только вы в вашей перегрузке делаете сначала is
, а потом каст, так что ни о какой производительности речи и вовсе нет.
Поэтому возникает закономерный вопрос, раз методы-расширения более обобщённые
… а они более обобщенные? В них уже можно делать паттерн-матчинг? Они взаимозаменяемы с switch...case
с идентичным синтаксисом?
и не уступают (а иногда и выигрывают) по производительнотсти, то зачем мне использовать встроенный, но весьма ограниченный синтаксический сахар?
Затем, что завтра MS возьмет и сделает оптимизацию для этого синтаксического сахара, благо это тривиально. А ваши методы где были, там и останутся.
Это пишет человек, которому только что было все равно на четырехкратное изменение?
Для вас пишу, ведь это вы так волнуетесь о производительности. Мне достаточно и того факта, что методы-расширения по порядку величин сопоставимы с нативной реализацией. А в 4 или в 1.2 раза отличие для меня не критично, это не в 100 раз и даже не в 10.
(x as T) ?? fallback. Только вы в вашей перегрузке делаете сначала is, а потом каст, так что ни о какой производительности речи и вовсе нет.
Если честно, я не сравнивал производительность именно этого метода с нативной реализацией (да и нативная реализация не поддерживает fallbackValue), но не думаю, что различия будут более чем в десятки раз. Если вы исследуете декомпиляции нативного PM, то увидите, что в разных сценариях он раскладывается в конструкции анологичные перегрузкам метода Is.
… а они более обобщенные? В них уже можно делать паттерн-матчинг? Они взаимозаменяемы с switch...case с идентичным синтаксисом?
Уже можно делать много интересных вещей, просмотрите примеры кода, связанные с Matching.
Затем, что завтра MS возьмет и сделает оптимизацию для этого синтаксического сахара, благо это тривиально. А ваши методы где были, там и останутся.
Почему раньше не сделали? Конечно, там умные люди работают, но вся система в целом иногда выдаёт странные и спорные решения. Поэтому не стоит слепо доверять даже авторитетам, а лучше всё переосмысливать самому.
Для вас пишу, ведь это вы так волнуетесь о производительности.
Ну то есть это не вы писали "применение метода-расширения более оптимально, чем родного PM, имплементация которого содержит подводный камень в виде упаковок для типов-значений"? Или у вас оптимальность по другому критерию? А по какому тогда?
Если честно, я не сравнивал производительность именно этого метода с нативной реализацией (да и нативная реализация не поддерживает fallbackValue)
Я же вам только что показал, как сделать с fallbackValue. Это называется "не поддерживает"? Да, там нет явного булевого признака, но он обычно не нужен, а если нужен — берем тернарный оператор.
не думаю, что различия будут более чем в десятки раз
Ну давайте посмотрим.
NegNativeWithBool | 10.247 ns
NegExtensionWithBool | 35.036 ns
NegNative | 5.367 ns
NegExtension | 15.600 ns
PosNativeWithBool | 11.878 ns
PosExtensionWithBool | 30.538 ns
PosNative | 5.435 ns
PosExtension | 13.855 ns
Стабильно раза в три-три с лишним ваше решение хуже.
Уже можно делать много интересных вещей
"Интересные вещи" — совсем не то же самое, что "обобщение".
Почему раньше не сделали?
Потому что нет лишних ресурсов.
Поэтому не стоит слепо доверять даже авторитетам, а лучше всё переосмысливать самому.
Ну тогда вы всегда будете в позиции догоняющего с вашим переосмыслением.
По fallbackValue, методу нужен булевый признак и он работает с value types, которые оператор 'as' не поддерживает.
Потому что нет лишних ресурсов.
Много же нужно ресурсов, чтобы вместо выражения
object.Equals(...)
подставить EqualityComparer<T>.Default.Equals(...)
«Интересные вещи» — совсем не то же самое, что «обобщение».
Рассматриваемые интересные вещи уже довольно-таки обобщены на широкий круг сценариев.
Примерно таких результатов я и ожидал — всё в допустимых пределах, различия нет даже в десять раз.
Ну нет, различие в разы — это не "в допустимых пределах". Заодно замечу, что у вас в любом случае создается объект (а в стандартном решении — только если нужен fallback), что тоже иногда недопустимо.
По fallbackValue, методу нужен булевый признак и он работает с value types, которые оператор 'as' не поддерживает.
Тернарный оператор, код тривиален.
Много же нужно ресурсов, чтобы
Вам уже неоднократно объясняли: да, много.
Рассматриваемые интересные вещи уже довольно-таки обобщены на широкий круг сценариев.
… осталось продемонстрировать, что они обобщаются на больший круг сценариев, чем встроеные языковые средства.
И для меня, в первую очередь, важна предсказуемость встроенных языковых средств, чтобы интуитивно заменив IModel на var где это, в привычном понимании, равноценная замена, не получить другое поведение программы.
Для моих задач это допустимые «в разы».
Но вы-то это предлагаете как общий паттерн, никак не оговаривая ограничений.
И для меня, в первую очередь, важна предсказуемость встроенных языковых средств, чтобы интуитивно заменив IModel на var где это, в привычном понимании, равноценная замена, не получить другое поведение программы.
Мы уже видели, что ваше "интуитивно" далеко не обязательно верно. Так и здесь: замена типа на var
далеко не всегда равноценна (вам пример, что ли, привести надо?), но вы почему-то хотите, чтобы это всегда было одним и тем же.
Ну и раз уж я расчехлил бенчмарк:
Method | Mean | Error | StdDev | Median |
------- |---------:|----------:|----------:|---------:|
Boxing | 13.99 ns | 0.3732 ns | 0.9224 ns | 13.64 ns |
Method | 11.14 ns | 0.3989 ns | 1.1573 ns | 11.11 ns |
Между i is 0
и i.Is(0)
разница порядка 20 процентов (в пользу i.Is(0)
).
Статья давняя, но поскольку обсуждение растянулось до наших дней, хотелось бы дать свой комментарий.
Оператор
if (GetPoint() is Point p)
вернет true если результат совместим с типом Point, иначе — если несовместимый тип или null — вернет false?
Тогда это ожидаемое и логичное поведение, т.к. "GetPoint() is Point p" по своей сути, сахар к традиционному паттерну C#:
Point p = GetPoint() as Point;
if (p != null) { }
При этом исходим из того, что функция GetPoint() объявлена как
object GetPoint()
т.к. для случая
Point GetPoint()
нет смысла использовать такую конструкцию для проверки на null.
А вот с
GetPoint() is var p
конечно, интереснее.
Попробуем записать это выражение в старом формате:
var p = GetPoint();
if (p != null) { }
И вот здесь мы видим расхождение в поведении старого и нового форматов проверки.
Если исходить из того, что var это вывод типа, то для декларации "object GetPoint()" выражение
GetPoint() is var p
должно быть эквивалентно:
GetPoint() is object p
(Для для частного случая случая "Point GetPoint()" будет та же история.)
Так почему для null возвращается true?
Тогда получается, что:
- выражение "x is var y" по своему поведению не соответствует выражению "x is SomeType y";
- так же не соответствует по поведению "старым" паттернам проверки (as, != null);
- и, наконец, что-то новенькое — в данном контексте var выполняет не статический вывод типов, а динамический.
Так может, тогда вообще не стоило добавлять конструкцию "x is var y"?
Или так:
- Добавить конструкцию "x is var y" с ожидаемым и логичным поведением (false в случае null).
- Для динамического вывода типа (если это уж так надо) добавить конструкцию "x is dynamic y" (всегда true)?
Это уже неоднократно обсуждалось в LDT. Рассматривайте конструкции x is y
как частный случай switch x {case y}
, тогда оно становится очевиднее.
в данном контексте var выполняет не статический вывод типов, а динамический.
Все так же статический. p
будет иметь тип object
для всех дальнейших операций.
Для динамического вывода типа (если это уж так надо) добавить конструкцию "x is dynamic y" (всегда true)?
В этом случае тип y
был бы dynamic
со всеми вытекающими отсюда. А он статический.
А теперь вернемся к задаче — мы хотим написать приблизительно следующее:
switch (GetPoint())
{
case Point p:
// вызывается только когда GetPoint вернул Point
case ... o:
// вызывается во всех остальных случаях, и нам нужно значение GetPoint
}
Что же нам поставить на месте ...
?
default var o
безо всякой дополнительной путаницы?switch (GetPoint())
{
case Point p:
// вызывается только когда GetPoint вернул Point
default var o:
// вызывается во всех остальных случаях, и нам нужно значение GetPoint
}
Да и правостороннее присваивание хорошо подходит для такого случая
//switch (GetPoint() to var o)
switch (GetPoint().To(out var o))
{
case Point p:
// вызывается только когда GetPoint вернул Point
default:
// можем использовать значение из GetPoint
}
Почему бы не поддержать намного более интуитивную конструкцию default var o безо всякой дополнительной путаницы?
Правда без путаницы?
switch (GetPoint())
{
case var x: //1
break;
default var x: //2
break
}
В каких случаях вызовется первое, а в каких — второе?
Да и правостороннее присваивание хорошо подходит для такого случая
Вот только оно вводит общую переменную, видную во всех кейсах, а зачем она мне?
Наоборот удобно, если мне нужно по особому обрабатывать null, то пишу
switch (GetPoint())
{
case var x: // logic for non null
break;
case null: // special logic for null
break
}
Причём, кейсы можно размещать в произвольном порядке! А в другом случае
switch (GetPoint())
{
default var x: // common logic for null and non null
break;
}
Вот только оно вводит общую переменную, видную во всех кейсах, а зачем она мне?IMHO Покрасивше выглядит, чем unexpected case var.
Не понимаю, что тут сложного… case var x будет срабатывать всегда, когда метод возвращает не null, в случае null сработает default var x.
"Сложного" здесь то, что теперь есть две конструкции, отличающиеся друг от друга минимально, с разным (но не всегда!) поведением.
Причём, кейсы можно размещать в произвольном порядке!
Правда в произвольном? А если вам надо сравнить с еще одной константой?
А в другом случае
Вот мне это неудобно. Потому что если у меня был кейс на отдельную обработку null
, а потом он перестал мне быть нужен, я хочу просто его удалить из switch
, и чтобы все работало.
IMHO Покрасивше выглядит
Да, я уже заметил, что вам нет дела до областей видимости переменных. Но тут дело не только в видимости, тут добавляется переменная, которая мне в большей части случаев не нужна (потому что каждый типизованный case
вводит свою).
unexpected case var.
В нем нет ничего неожиданного.
«Сложного» здесь то, что теперь есть две конструкции, отличающиеся друг от друга минимально, с разным (но не всегда!) поведением.Да? А как насчёт минимально отличающихся с разным, но не всегда, поведением?
switch (GetPoint())
{
case object x:
break;
case var x:
break
}
На мой взгляд,
switch (GetPoint())
{
case var x:
break;
default var x:
break
}
либо
switch (GetPoint())
{
case object x:
break;
default object x:
break
}
существенно более интуитивно, благодаря ключевому слову default.
В нем нет ничего неожиданного.Для вас нет, а для меня это «грязный» синтаксис, которого я просто избегаю.
Да? А как насчёт минимально отличающихся с разным, но не всегда, поведением?
Разная спецификация типа — это для вас минимально отличающаяся запись?
(кстати, а когда поведение case object x
и case var x
не будет отличаться?)
Для вас нет, а для меня это «грязный» синтаксис, которого я просто избегаю.
… предлагая ввести другой синтаксис, который ничем не чище.
(мне нравится, как вы элегантно проигнорировали вопрос про порядок кейсов)
Разная спецификация типа — это для вас минимально отличающаяся запись?var для меня означает лишь вывод типа из контекста выражения, а не разную спецификацию. Тот факт, что на него навесили странную дополнительную логику, нарушив принципы разделения ответственности и грамотной композиции, лежит уже на совести архитекторов. Я просто не поддерживаю их решение, используя свои кастомные модификации с чистой интуитивной имплементацией.
Долго пытался выяснить объективные причины, зачем это было необходимо и почему не сделали более грамотно, но один из архитекторов языка (признав, что это хоть и болячка) мотивировал решение острой необходимостью данной конструкции в выражениях вида is {… } x и case {… } x и тем, что команда дизайна верит в это решение. К сожалению, на аргументированную критику и предложенный альтернативный и более функциональный Check-паттерн (аналог With) никто из команды мне больше не ответил.
Правда в произвольном? А если вам надо сравнить с еще одной константой?Произвольном в контексте данного примера, если вы меняете пример, то порядок может иметь значение.
var для меня означает лишь вывод типа из контекста выражения, а не разную спецификацию.
Ну так вывод из контекста — это и есть иная (неявная) спецификация, в противовес явному указанию типа.
Тот факт, что на него навесили странную дополнительную логику, нарушив принципы разделения ответственности и грамотной композиции
Не "нарушив принципы", а "вы не понимаете использованных принципов" (а иначе вам придется сейчас объяснять, как вы применяете SRP к языку программирования. Со ссылками на авторитетные источники).
Долго пытался выяснить объективные причины, зачем это было необходимо и почему не сделали более грамотно
Не "более грамотно", а "как вам кажется более грамотно".
К сожалению, на аргументированную критику
Я читал эту дискуссию, если что, и никаких объективных аргументов вы в своей критике не привели.
предложенный альтернативный и более функциональный Check-паттерн (аналог With) никто из команды мне больше не ответил.
Во-первых, вы не доказали, что он более функциональный. Во-вторых, какого ответа вы ожидаете от LDT по поводу паттерна (еще, кстати, надо доказать, что он паттерн), который реализуется не на уровне языка, а на уровне разработки?
Произвольном в контексте данного примера, если вы меняете пример, то порядок может иметь значение.
А теперь давайте вспомним, что проверки на константы должны идти раньше проверок на типы, а null
— это тоже константа. И где тут логика?
Я читал эту дискуссию, если что, и никаких объективных аргументов вы в своей критике не привели.Вы не читали личную переписку. А также я вам присылал ссылку на довольно объективное исследование.
И ещё такой момент — здесь много «корпоративной политики», ибо зачем блокировать в репозитории пользователя, который вовсе не нарушал правил поведения, а просто отстаивал альтернативную точку зрения? Тем более, если она настолько слабая и не выдерживает авторитетной критики…
А теперь давайте вспомним, что проверки на константы должны идти раньше проверок на типы, а null — это тоже константа. И где тут логика?Не должны, C# допускает произвольный порядок проверок на типы и значения. Как их размещать, в зависимости от требуемой логики, дело разработчика.
А также я вам присылал ссылку на довольно объективное исследование.
Ну так я об этом и говорю: там нет никакой аргументации, там есть набор утверждений, большая часть из которых ничем не обоснована.
зачем блокировать в репозитории пользователя, который вовсе не нарушал правил поведения, а просто отстаивал альтернативную точку зрения? Тем более, если она настолько слабая и не выдерживает авторитетной критики…
Ровно затем, чтобы он перестал спамить своей точкой зрения, невзирая на критику.
Не должны, C# допускает произвольный порядок проверок на типы и значения.
Правда?
switch (new object())
{
case int s:
return;
case 5:
return;
}
Дает "error CS8120: The switch case has already been handled by a previous case."
Так никогда не пробовали написать?
switch (new object())
{
case string s:
return;
case 5:
return;
}
Увы, ваши утверждения насчёт is var / case var обоснованы ещё меньше.
Я и не утверждаю, что это объективные аргументы.
Так никогда не пробовали написать?
Пробовал, но это не отменяет того, что константы должны идти раньше покрывающего их типа. Кстати, написать case null
после case var x
тоже нельзя — и если бы case var x
поддерживал вашу логику, тем более было бы нельзя.
Пробовал, но это не отменяет того, что константы должны идти раньше покрывающего их типаНе так. Любой кейс дожен просто идти до более обобщённого и всё, не зависимо от того, константа там или тип.
switch (new object())
{
case int s when s > 9:
return;
case 5:
return;
}
и если бы case var x поддерживал вашу логику, тем более было бы нельзяНет, вы путаете. Это рабочий код
switch (new object())
{
case object o:
return;
case null:
return;
}
И по моей логике он должен быть абсолютно эквивалентен
switch (new object())
{
case var o:
return;
case null:
return;
}
Увы, текущая реализация в C# этому не соответствует.
Не так. Любой кейс дожен просто идти до более обобщённого и всё, не зависимо от того, константа там или тип.
Именно, что "так", я описал конкретное условие (не единственное). Константы должны идти раньше покрывающего их типа, а int x when x > 9
— это не "покрывающий тип", это сложное условие. Как вы думаете, что будет, если написать так:
switch (new object())
{
case int x when x > 0:
Console.WriteLine("Positive");
break;
case 5:
Console.WriteLine("Five");
break;
}
(очень поучительно посмотреть на релизный код при switch(6)
и switch(5)
)
Нет, вы путаете.
Да, я действительно путаю, извините.
Именно, что «так», я описал конкретное условие (не единственное). Константы должны идти раньше покрывающего их типа...В вашей изначальной формулировке вы упустили важное уточнение «покрывающего их типа», поскольку в общем случае типы и константы могут и не перекрывать друг друга.
А теперь давайте вспомним, что проверки на константы должны идти раньше проверок на типы, а null — это тоже константа. И где тут логика?А что касается null, то никакой тип не может его перекрыть.
Как вы думаете, что будет, если написать так:Мы надуем наш умный компилятор! :)
(очень поучительно посмотреть на релизный код при switch(6) и switch(5))Ничего удивительного, компилятор удаляет недостижимые ветви кода.
В вашей изначальной формулировке вы упустили важное уточнение «покрывающего их типа», поскольку в общем случае типы и константы могут и не перекрывать друг друга.
А, простите, покрывающий что еще может быть тип?
А что касается null, то никакой тип не может его перекрыть.
Это не вполне так: формально null
входит в множество допустимых значений любого reference-типа.
Мы надуем наш умный компилятор!
Это не "надуем", это демонстрация того, что не "любой кейс должен идти раньше более общего".
Ничего удивительного, компилятор удаляет недостижимые ветви кода.
Правда?
switch(5)
{
case int x when x > 0:
Console.WriteLine("pos");
return;
case 5:
Console.WriteLine(5);
return;
}
выдает
if (5 > 0)
{
Console.WriteLine("pos");
}
else
{
Console.WriteLine(5);
}
Правда же, все ветки кода достижимы?
Еще веселее, поставим в switch
-1:
if (-1 > 0)
{
Console.WriteLine("pos");
}
Тоже, конечно, все ветки достижимы.
А, простите, покрывающий что еще может быть тип?Имею в виду следующее, типы int и object покрывают значение 5, а вот string, например, это значение уже не покрывает.
switch (new object())
{
//case int i: break;
//case object o: break;
case string s: break;
case 5: break;
}
Это не вполне так: формально null входит в множество допустимых значений любого reference-типа.Верно. Но я говорю про перекрытие именно в контексте оператора switch.
Насчёт странных и непредсказуемых декомпиляций оператора switch ничего толкового не скажу, эти вопросы лучше направить разработчикам компилятора. :)
Имею в виду следующее, типы int и object покрывают значение 5, а вот string, например, это значение уже не покрывает.
Это не влияет на мое утверждение о константах.
Насчёт странных и непредсказуемых декомпиляций оператора switch ничего толкового не скажу
Ну вот и не высказывайтесь по поводу этого поведения тогда.
Но причины на самом деле во многом политические — просто критика в некотором роде подрывает авторитет разработчиков языка и «мешает» им работать. Если бы критические комментарии и аргументы были очевидно слабы, то почему бы их просто не проигнорировать? Зачем-то понадобилось блокировать человека… )
Во-первых, это вы считаете ваши комментарии аргументированной критикой. Во-вторых, если критика не связана с темой обсуждения — это все еще оффтоп.
Ну, под определения спама или оффтопа мои комментарии уж точно не попадают: они тесно связаны с темой обсуждения, нейтральны, содержат лишь технические детали.
Я же говорю: это вы так считаете.
Более того, на них никто не обязан отвечать…
На спам тоже никто не обязан отвечать.
Я же говорю: это вы так считаете.Считаю.
На спам тоже никто не обязан отвечать.Удивительно много людей отвечало на мой «спам».
Удивительно много людей отвечало на мой «спам».
Удивительно много людей отвечает на спам в почте.
Всё проще. Нет у меня подобных манихейских замашек, я всего лишь делюсь личным опытом.
Но отчего же вы до сих пор отвечаете на мой «спам» и выражаете своё мнение на его счёт?
Потому что мне так хочется.
Нет у меня подобных манихейских замашек, я всего лишь делюсь личным опытом.
Ничего удивительного в том, что кто-то считает, что этот опыт не интересен в определенном сообществе.
Далее, создавать кучу аккаунтов и писать на личные скрытые почты LDM — это все чисто спамерские техники. Когда вас забанили на 100500 повторении одного и того же, имело смысл подумать, почему так произошло, а не говорить себе «ну, они нифига не поняли, ЩА Я ИМ ОБЪЯСНЮ».
Вы не дискутируете. Вы выставляете ультиматум «СДЕЛАЙТЕ ВОТ ТАК», и не приемлете никакого иного решения. А если не сделают, то я заспамлю вам почту, аккаунты, буду подстерегать вас после работы и выскакивать из кустов с криком «ВЫ ВСЕ НЕПРАВЫ, НАДО БЫЛО ДЕЛАТЬ ВОТ ТАК».
Там не было аргументированной критики. Вы просто повторяли одну шарманку «так писать плохо» и полностью игнорировали то, что вам пишут ответ.Вероятно, вы не совсем внимательно читали мои комментарии и вникали в суть сказанного.
на личные скрытые почты LDMНемногочисленные письма были отправлены лишь на личные открытые почты.
Вы не дискутируете. Вы выставляете ультиматум «СДЕЛАЙТЕ ВОТ ТАК», и не приемлете никакого иного решения...С чего вы вообще взяли, что я ставлю ультиматумы и говорю, кому что делать? Меня по большей части интересуют поиски математической красоты в программировании, а не доказывание своей правоты. При наличии убедительных аргументов я легко признаю свои ошибки, что не раз случалось в дискуссиях, но пока таких аргументов нет — отстаиваю свою точку зрения, и только.
Вероятно, вы не совсем внимательно читали мои комментарии и вникали в суть сказанного
Вероятно, если несколько людей читают в ваших комментариях одно и то же, стоит задуматься о том, что вы в них пишете?
При наличии убедительных аргументов я легко признаю свои ошибки, что не раз случалось в дискуссиях, но пока таких аргументов нет — отстаиваю свою точку зрения, и только.
Вот только убедительными аргументами вы считаете исключительно то, что вам удобно.
Вероятно, если несколько людей читают в ваших комментариях одно и то же, стоит задуматься о том, что вы в них пишете?Для себя я сделал такой вывод, да, есть люди, которые внимательно и вдумчиво читают комментарии, но есть и немало тех, кто слепо ориентируется на решения авторитетов и мнение большинства, не особо углублясь в детали, но тем самым поддерживая это большинство.
Отдельный вопрос — люди-авторитеты (например, из LDM). Мне было очень интересно, способны ли они открыто признавать своё неудачное решение или наоборот привести убедительные аргументы в его пользу. К сожалению, веского объяснения, почему сделано именно так, никто не привёл.
Интересно, что по последней информации LDM недавно реализовало в прототипе вместо p is { Name is «Abc» } другую форму p: { Name: «Abc» } для сохранения смысла операторов is (==, =), так что мои препирательства, возможно, оказали какое-то влияние.
Вот только убедительными аргументами вы считаете исключительно то, что вам удобно.Как вам объяснить… Есть симметрия и математическая красота — это основные ориентиры для меня. Если вижу их очевидое нарушение, то прямо об этом говорю, здесь нет речи об удобстве или неудобстве аргументов для меня. В конце концов нашлись действительно строгие альтернативные решения, так что со спокойной душой я могу писать чистый код по своему разумению.
К сожалению, веского объяснения, почему сделано именно так, никто не привёл.
И снова: никто не привел объяснения, которое показалось бы вам веским. А теперь, благодаря вашему поведению, нельзя найти даже те объяснения, которые были приведены.
Есть симметрия и математическая красота — это основные ориентиры для меня.
И ни один из этих ориентиров не является объективным, и тем более — применительно к разработке ПО. Больше чем в одной дискуссии видно, что под "математической красотой" вы и собеседники понимаете разное.
Если вижу их очевидое нарушение,
Это и есть "мне не нравится", ваш любимый аргумент.
В конце концов нашлись действительно строгие альтернативные решения,
Хаскель?
я могу писать чистый код по своему разумению.
Вот именно что "по вашему разумению". То, что другим ваш код чистым не кажется, вас не волнует.
Хаскель?C#
(с заменой is var, case var на кастомные расширения)
То, что другим ваш код чистым не кажется, вас не волнует.Неплохой у меня код, уж я-то знаю. ;)
C# (с заменой is var, case var на кастомные расширения)
Ну то есть не "действительно строгие", а "те, которые я считаю строгими".
Неплохой у меня код, уж я-то знаю
Я и говорю: мнение других вас не волнует.
Я и говорю: мнение других вас не волнует.Меня волнуют аргументированные мнения.
… а аргументированность вы определяете самостоятельно по субъективным критериям, вот круг и замкнулся.
Если я пишу
Person GetPerson() => ...
GetPerson().Is(out Person p)
и далее естественным образом заменяю на
GetPerson().Is(out var p)
с полным сохранением логики работы, то закономерно ожидаю аналогично симетрии для случаев
GetPerson() is Person p
GetPerson() is var p
Увы, эта симетрия нарушена… Почему? Может, вы мне поясните примерами кода, где это крайне необходимо?
Аргументировать я предлагаю кодом, потому что это наиболее строгий подход — это как вывод формул в математике.
А ничего, что для кода не определены правила эквивалентного преобразования, которыми вы (предположительно) пользуетесь при выводе формул в математике?
и далее естественным образом заменяю на
Естественный образ — субъективное понятие.
то закономерно ожидаю
А на основании чего вы "закономерно" ожидаете одинаковой работы между каким-то методом и оператором?
закономерно ожидаю аналогично симетрии для случаев
Угу, а определение симметрии для кода вы тоже уже ввели?
Почему? Может, вы мне поясните примерами кода, где это крайне необходимо?
Вам не надоело еще видеть этот пример?
switch(GetPerson())
{
case Employee p:
//only when p is Employee
break;
case var p:
//all other cases
break;
}
для кода не определены правила эквивалентного преобразованияМожет, не помешало бы их ввести? Как думаете?
Вам не надоело еще видеть этот пример?Встречный вопрос, не надоело?
switch(GetPerson())
{
case Employee e:
//only when e is Employee
break;
default var p:
//all other cases for p
break;
}
switch(GetPerson().To(out var p))
{
case Employee e:
//only when e is Employee
break;
default:
//all other cases for p
break;
}
Может, не помешало бы их ввести? Как думаете?
Может быть, но пока я не видел ни одной успешной попытки (ну, не считая правил рефакторинга).
Встречный вопрос, не надоело?
GetPerson().To(out var p)
Ненужный захват p
в глобальной области видимости меня не устраивает.
Вот мы и пришли к "на мой взгляд". А как же объективные аргументы?
(ну и да, проблему default var
тоже уже разбирали)
Проблем с default var не больше, чем с case var.
И где у case var
проблема с конфликтом default var
и case var
?
Мне неинтересны аргументы, почему такое решение не подойдёт сейчас, я сам всё это прекрасно понимаю.
Вы мне объясните, зачем сделали именно так, а не иначе… В чём проблема сохранения прежнего значения var?
Так в том и дело, зачем было вообще создавать этот конфликт и нарушать симетрию, если можно было сразу всё сделать красиво?
Ну так его и не создали.
Вы мне лучше на этот вопрос ответьте, а то ходите по кругу: is var всегда true, потомучто case var всегда true, а default var не подойдёт, потому что есть уже case var.
Вы не понимаете проблемы, кажется: default var
будет конфликтовать с case var
вне зависимости от поведения case var
.
Вы мне объясните, зачем сделали именно так, а не иначе…
Чтобы цепочка кейсов выглядела консистентной.
В чём проблема сохранения прежнего значения var?
Какого именно прежнего значения?
Вы не понимаете проблемы, кажется: default var будет конфликтовать с case var вне зависимости от поведения case var
С чего вдруг?
case var матчит любой случай кроме null
default var матчит любой случай вместе с null
Даже удобнее получается.
Какого именно прежнего значения?Вывод типа и только.
С чего вдруг?
case var матчит любой случай кроме null
default var матчит любой случай вместе с null
С того, что эти два кейса визуально сложно отличить. В этом и конфликт.
Заодно рекомендую помедитировать, как они себя поведут для non-nullable-выражения.
Вывод типа и только.
Во-первых, это некорректное значение: изначально var
обозначает декларацию переменной (тип которой будет выведен).
Во-вторых, вывод типа никуда не делся.
case var
case object
Так себе аргумент с визуальной сложностью.
Во-вторых, вывод типа никуда не делся.В-третьих, добавилось неочевидное поведение с null.
то закономерно ожидаю аналогично симетрии для случаев
Почему ожидаете?
как вывод формул в математике.
При этом в математике, скажем, активно используется правило подстановки, которое в вашем коде невозможно — но вас это не смущает.
Серьезно?
var x = SomeFunction(); //pure
Do(x);
Other(x);
полностью семантически эквивалентно
Do(SomeFunction());
Other(SomeFunction());
(более того, оптимизирующий компилятор может сам сделать оптимизацию как описано выше)
А теперь:
var x = SomeFunction().To(out var p).DoSomethingWith(p);
Do(x);
Other(x);
Подставляем:
Do(SomeFunction().To(out var p).DoSomethingWith(p));
Other(SomeFunction().To(out var p).DoSomethingWith(p));
Не компилируется.
Do(SomeFunction().To(out var x).DoSomethingWith(x));
Other(SomeFunction().To(out var y).DoSomethingWith(y));
или раз уж на то пошло
SomeFunction().To(out var p); //pure
Do(SomeFunction().DoSomethingWith(p));
Other(SomeFunction().DoSomethingWith(p));
Да и вообще хорошо, что ваш пример не компилируется, поскольку в C# методы в основном не чистые да и многие типы изменяемые, что защищает от написания неоптимального кода.
Попробуйте так
Так это и есть нарушение правила подстановки.
Кстати.
SomeFunction().To(out var p); //pure
Нет, не pure — есть побочный эффект.
var x = SomeFunc(); //pure
Do(x.DoSomethingWith(x));
Other(x.DoSomethingWith(x));
нет здесь побочных эффектов?
Наверное, так
Do(SomeFunc().DoSomethingWith(SomeFunc()));
Other(SomeFunc().DoSomethingWith(SomeFunc()));
Вы убрали
var x =
. Так уберите To(out var p)
и получите тот же результат.нет здесь побочных эффектов?
Зависит от того, есть ли побочные эффекты у DoSomethingWith
.
Вы убрали var x =.
Подождите, где я убрал var x
?
Подождите, где я убрал var x?
var x = SomeFunction(); //pure
Do(x);
Other(x);
полностью семантически эквивалентно
Do(SomeFunction());
Other(SomeFunction());
Я не убрал var x
. Я заменил x
на выражение справа от =
, точно так же, как это происходит, скажем, в системах уравнений. Дадада, математическая красота.
var x = SomeFunction(); //pure
SomeFunction() to var x; //pure
и повторяйте все ваши манипуляции с ним…
Тогда уж вводите оператор правостороннего присваивания для полной математической красоты
В математике нет "правостороннего присваивания". Там, собственно, и присваивания-то особо нет...
Но, что важнее, я, в отличие от вас, не гонюсь за "математической красотой" в императивном языке. Видимо, первый пример надо было давать не на C#, а в виде уравнения.
Скажите пожалуйста, каким способом вы предлагаете матчить произвольное значение, и приведите пример, как это будет выглядеть в случае подпаттернов.
неочевидное смешение матчинга по типу и по значению (например, в F# они разделены)
Ээээ, что? A вот это:
match shape with
| Square(2, 2) -> 1
| Circle(4, 5) -> 0
это по типу или по значению?
Как по вашему должен работать такой псевдокод
match shape with
| Square(2, 2) -> 1
| Circle(4, 5) -> 0
| var(0, 0) -> 3 // Shape(0, 0) -> 3
где базовый Shape (выведенный тип для var) поддерживает деконструкцию?
где базовый Shape (выведенный тип для var) поддерживает деконструкцию?
Погодите, это вы предлагаете, чтобы var что-то там выводил и поддерживал, вы и объясняйте. В текущей реализации то, что вы написали просто не скомпилируется, это некорректный паттерн.
Это грамотная композиция, а не смешение.
… и как вы объективно отделяете одно от другого?
Обратите внимание, раньше вы говорили, что они разделены, теперь вы говорите, что они скомпонованы. А часа не прошло.
Как по вашему должен работать такой псевдокод
Никак, в F# такая конструкция не поддерживается (и я не слышал, чтобы она была в планах).
Обратите внимание, раньше вы говорили, что они разделены, теперь вы говорите, что они скомпонованы. А часа не прошло.Они не смешаны в F#.
Исходя исключительно из вашего понимания того, что такое смешанность. Вам удобно говорить, что в F# они не смешаны, а в C# — смешаны, вот вы так и говорите. Но где формальный критерий?
Они не смешаны в F#.
Утверждение "в F# они разделены" больше недействительно?
Я вам конкретный вопрос задал. Какой синтаксис вы предлагаете в качестве альтернативы нынешнему SomeRecord(var x, var y, var z)? Четко и ясно напишите, пожалуйста.
var result = someThing switch {
SomeRecord(var x, var y, var z) => SomeFun(x, y, z),
_ => throw new InvalidOperationException()
};
ну или
if (someThing is SomeRecord(var x, var y, var z)) {
result = SomeFun(x, y, z);
} else {
throw new InvalidOperationException();
};
var result = someThing.Match(
(SomeRecord r) => SomeFun(r.x, r.y, r.z),
(object o) => throw new InvalidArgumentException()
);
null можно обработать двумя способами
someThing?.Match
либо
() => throw new InvalidArgumentException()
Ага, два делегата на пустом месте.
Можно реализовать и одним делегатом, но мне больше нравится отделять проверку на null.
Кошмар.
Когда у вас этот матчинг воткнут внутрь какого-нибудь актора, который должен очень быстро обрабатывать сообщения, и матч-кейсов много — да, это кошмар.
Можно реализовать и одним делегатом
А если у вас несколько матч-кейсов?
Когда у вас этот матчинг воткнут внутрь какого-нибудь актора, который должен очень быстро обрабатывать сообщения, и матч-кейсов много — да, это кошмар.Тут уже возникает вопрос, а стоит ли вообще применять стандартный матчинг в swich с его подводными упаковками…
А если у вас несколько матч-кейсов?Не понял вас, то одним делегатом нужно, то уже двумя.
Тут уже возникает вопрос, а стоит ли вообще применять стандартный матчинг в swich с его подводными упаковками…
Ну да, потому что у меня все равно все сообщения — референс-типы.
Не понял вас, то одним делегатом нужно, то уже двумя.
Мне нужно вообще без делегатов.
Вывод типа и только.
Впрочем, если быть совсем точным, var
— это контекстно-зависимое ключевое слово: "When the local-variable-type is specified as var
and no type named var
is in scope, the declaration is an implicitly typed local variable declaration".
(SomeRecord r)
Это не рекурсивный паттерн. Я хочу иметь возможность деструктурить объект внутри. Как в предлагаемом вами синтаксисе будет выглядеть конкретно SomeRecord(var x, var y, var z)? Как написать, например SomeRecord(var x, 10, var z)? Именно в этом и есть смысл паттерн-матчинга — в деструкции объектов. Не в фильтрах. Для того, чтобы просто фильтровать объекты внутри ифа или свитча по типу никакого паттерн-матчинга нахрен не надо.
(SomeRecord r) =>
{
var (x,y,z) = r;
SomeFun(x, y, z);
}
Матчинг по значениям можно добавлять, например, так
(Person p) =>
p.ToSwitch(
p.FirstName.To(out var name),
p.Age.To(out var age)
).Is(out var sw) &&
sw.Case("Jack", 32) ? "Jack is 32" :
sw.Case("Alice", 16) ? "A is 16" :
sw.Case("Piter", age) ? "P age" :
sw.Case(name, 12) ? "young" :
sw.Case(name, age) ? "any" :
"unreachable",
Особенно круто выглядит цепочка sw.Case
внутри незаметного тернарного оператора. Я такой код, извините за аргумент, сразу разворачиваю на код-ревью, потому что это гарантированная мина.
Кстати, а как сопоставляются аргументы sw.Case
со свойствами Person
?
person switch {
(Person "Jack", 32) => "Jack is 32",
(Person "Alice", 16) => "A is 16",
(Person "Piter", _) => "P age",
(Person _, 12) => "young",
(Person _, _) => "any",
}
?
Извините, но Вы — наркоман.
модератры нажаловались в поддержку гитхаба, дабы были приняты меры против неугомонного «спамера».
И правильно сделали.
Если бы критические комментарии и аргументы были очевидно слабы, то почему бы их просто не проигнорировать?
Потому что они мешают конструктивной дискуссии.
Вот только сам я и начинал те дискуссии, из-за которых меня изначально решили заблокировать. И, замечу, подавляющее большинство моих комментариев были всего лишь аргументированными ответами на сообщения других пользователей, никого не призывал вливаться в дискуссии.
Вот только сам я и начинал те дискуссии, из-за которых меня изначально решили заблокировать.
И что? Это как-то делает их более полезными для сообщества?
подавляющее большинство моих комментариев были всего лишь аргументированными ответами на сообщения других пользователей
Во-первых, даже аргументированный ответ не обязательно является конструктивным.
Во-вторых, вы меня извините, но я уже достаточно долго наблюдаю за вашим уровнем аргументации, и по моим наблюдениям он практически всегда сводится к "мне так больше нравится".
Ну а в-третьих, поскольку благодаря вашему поведению мы уже не увидим этих комментариев, никакого способа подтвердить ваши слова у вас нет.
Ну а в-третьих, поскольку благодаря вашему поведению мы уже не увидим этих комментариев, никакого способа подтвердить ваши слова у вас нет.
Это трагедия — никто не поверит мне… ))
Знаете, меня это мало волнует, свою функцию дискуссии выполнили сполна. По началу в аргументах и предлагаемых решениях, действительно, были слабые места, но благодаря критике сообщества, указавшей на них, многие пробелы были успешно устранены.
Кроме того, кастомные решения во многом я применяю для своих нужд, они меня устраивают даже больше встроенных, а в статьях просто делюсь наработками с другими людьми, и, судя по голосованиям и просмотрам, эти исследования интересны также другим разработчикам. Вот и всё.
Мне любопытно слушать разные мнения, и я открыт к дискуссиям, но на текущий момент существенных недостатков в рассмотренных решениях не вижу. Хотите — испольуйте их, хотите — нет, мне от этого не холодно и не горячо. )
Знаете, меня это мало волнует, свою функцию дискуссии выполнили сполна.
Свою функцию для вас, а не для сообщества.
на текущий момент существенных недостатков в рассмотренных решениях не вижу
Собственно, в этом и проблема.
Собственно, в этом и проблема.Ну, вы так и не предоставили убедительных для меня аргументов, обнаруживающих существенные недостатки, из-за которых мне стоило бы отказаться от своих кастомных решений.
Учитывая, что степень убедительности аргументов определяете вы сам, и вы не предоставляете никаких формальных способов это оценить — это неудивительно.
Впрочем, у меня и не было цели убедить вас.
Исправлять ли unexpected behavior в C# 7 или оставить как есть, усложнив синтаксис языка для компенсации?