Как стать автором
Обновить

Комментарии 291

Оператор is однозначно говорит, о том, что объект является таким-то типом. Что говорит is var? Не понимаю зачем это нужно вообще было делать.

Лично для меня на интуитивном уровне это что-то вроде деконструкции метода 'Is' со следующей имплементацией (для декларации переменных в самом выражении):

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.
Вывод типа можно применить не везде, а когда можно, то зачем тогда вообще is?
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) {} //  
Для 'bar' будет выведен тип 'object' (поскольку метод возвращает 'object').

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) {}

Метод 'GetPoint()' возвращает 'Point' либо 'null', поэтому компилятор может вывести тип по сигнатуре метода.

В 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' зависит от контекста.
var p = GetPoint(); // только вывод типа
if (GetPoint() is var p) ...
// условно эквивалентно 
if (GetPoint() is null or Point p) ...
К тому говорю, что, на мой взгляд, было бы более очевидно и корректно применить другое имя для оператора, например, назвать его 'any' подразумевая 'null or var' (в классическом значении)
if (GetPoint() is any p) ...
if (GetPoint() is null or Point p) ...
if (GetPoint() is null or var p) ...


Ровно наоборот. В выражении var p = GetPoint() var должен принимать любое значение, которое может вернуть GetPoint. Так что значение var "прибито гвоздями", нужно что-то типа


if (GetPoint() is nonnull p) 
Еще немного, и Delphi наконец напишут :) С учетом того, что автор языка один, можно особо не удивляться. Только зачем шарп в будущем, если есть Delphi и уже давно? :)
НЛО прилетело и опубликовало эту надпись здесь
Верно, спасибо! Я не очень хорошо знаю английский. Исправил на «There is null.», так будет корректно?
Возможно предлагалось использовать It вместо There (It is not point., It is null.).
> однако по предлагаемым ссылкам значимых аргументов в защиту принятого решения мне найти, к сожалению, так и не удалось, оно просто постулировалось.

Так работает паттерн-матчинг во всех языках. 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";
}

В данном случае вводится переменная внутри выражения.

object вместо var было бы логичнее…

Логичнее до тех пор пока метод возвращает object.
Если взять пример:
Rect: Shape
Triangle: Shape
Circle: Shape
то тип переменной будет Shape, а не object.

… и получить боксинг на пустом месте?

Предлагаю вместо 'var', у которого уже есть устоявшееся интуитивное значение, использовать другое ключевое слово, например: 'any', 'all', 'let' или хотя бы выражение 'null or var'.
Этот паттерн — самый распространенный, так что всякие сложные выражение вроде 'null or var' сразу нет, он должен быть простым и коротким, «дефолтным», так сказать. Матчить произвольное значение — это то, что мы хотим от паттерн-матчинга по умолчанию.
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?

Статические методы можно вызывать на объекте, который равен null.
Это фишка extension методов, потому как в реальности метод вызывается не на обьекте, а в статическом классе, который их хранит, а обьект в свою очередь передается лишь как первый параметр.
Немного дополню ваш ответ для любопытствующих: любой вызов метода от объекта происходит через обращение к сгенерированному для типа статическому классу, в который неявно отдаётся рабочий объект. Разница проявляется на уровне MSIL кода: для статических методов (в том числе extension) используется OP-код «call», который просто вызывает метод без каких-либо проверок, тогда как для обычных методов используется «callvirt» (при условии, что компилятор уверен, что проверка в данном контексте имеет смысл, иначе соптимизирует в call), который сначала проверяет объект на null и при случае выкидывает NullReferenceException.
В C# 7 незаметным образом, на мой взгляд, просочилась подмена значения оператора 'var', теперь это может значить что-то ещё…

Не таким уж и незаметным: Pattern Matching, раздел "var declarations in case expressions". Единственное, что оттуда не очевидно, это то, что это точно так же применимо к оператору is.

Лично на мой взгляд, стоило ввести новое ключевое слово или добавить какой-то модификатор к 'var', а не просто подменять его значение, в зависимости от контекста использования. Тогда бы никаких неожиданностей не возникало.

Модификатор — это и есть "контекст использования", так что ничего не меняется. Новое ключевое слово, конечно, круто, но тогда были бы неконсистентные декларации (да и новые ключевые слова — это breaking change).

Сейчас, кажется, всё относительно консистентно, просто 'var' в разных контекстах имеет отличающийся смысл, что не интуитивно и иногда сбивает с толку. На мой взгляд, на консистентность не должно было бы повлиять введение новых ключевых слов для определённых контекстов.

Конечно, если добавлять новые ключевые слова уже сейчас, после релиза, то да — это 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. Что будет, когда он перекомпилирует свой код под новой версией языка? Правильно, смена поведения (даже не ошибка компиляции).

Сейчас уже да, верно, но если бы 'any' было введено вместе с текущей реализацией pattern-matching'а, то синтаксис 'is any x' [где any — тип] был бы невалиден до того, поэтому введение ключевого слова в контексте нового синтаксиса ничего бы не поломало. По крайней мере, я не могу придумать пример, где бы это ещё могло проявиться.
P.S. Выражение 'o is any' можно было бы расценивать в старом значении [проверка типа], но новое 'o is any x' уже означало бы иное поведение.

Вот это как раз была бы адская неоднозначность.

Почему же неоднозначность? Ключевое слово 'any' без 'x' употребляться не может само по себе.

Более того, ради интереса сейчас проверил, можно ли объявить класс с именем 'var'. Можно! Всё компилируется, вот только 'var' в обычном значении перестаёт работать, поскольку воспринимается как тип, а ведь когда-то же и 'var' не было в C#.
Почему же неоднозначность? Ключевое слово 'any' без 'x' употребляться не может само по себе.

Потому что стоит забыть переменную и поведение радикально меняется.


ведь когда-то же и 'var' не было в C#.

Ну так это и был breaking change.

Не спорю с тем, что поведение радикально меняется, но старый код (без изменений) будет работать по-прежнему, поэтому формально это не breaking change, обратная совместимость сохранена.

Конечно, для разработчика нововведение может выглядеть ужасно непредсказуемо и даже ломать его логиченые и интуитивные мыслительные шаблоны, как произошло у меня с 'var', но, скорее, это можно назвать pattern breaking. :)
По крайней мере, я не могу придумать пример, где бы это ещё могло проявиться.

Если я ничего не путаю, все другие места с этим ключевым словом все равно потребуют эскейпинга.

Интересно. Не использую пока 7.0, но судя по всему тема уже давно разжеванна (то, что вы пользуетесь if вместо switch/case, значения не имеет). У null нет типа, var может быть чем угодно, включая null, следовательно pattern-matching c var всегда возможен. Думайте об «is var» как о default в switch/case.

Это не unexpected, а скорее «невыученный» behavior. В любом языке их полно, маленькие и большие нюансы, любой из которых unexpected для новичка. C нетерпением жду перехода нашего отдела на VS 2017 и свежих граблей!

P.S.: is any — интересная идея, но, как вы понимаете, запоздалая, т.к. is var уже используют и вряд ли кто-то обрадуется несовместимости нового C#.
is Type гарантирует, что там не null, а is var ничего не гарантирует. Получается, что is var работает, как as Type. А нафига?
Получается, что is var работает, как as Type. А нафига?

Для общности с case var x.

> Получается, что is var работает, как as Type. А нафига?

Он нужен для матчинга произвольного аргумента в подпаттернах.
в идеале (то есть не обращая внимания на совместимость) и имея на уме not null reference из C# 8 имеет смысл такой синтаксис:

if (MyMethod() is var x) // x is not null
if (MyMethod() is var? x) // x can be null

На мой взгляд, довольно очевидный вариант в контексте C# 8 может выглядеть так
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 вместо уже имеющегося очевидного?
> `a is var b` вообще для меня довольно бесполезный паттерн альтернативного объявления переменной

Он будет нужен в подпаттернах.
Может, для вас он очевиден, но для большого числа других людей, увы, нет.
Почему вы хотите ВВЕСТИ в язык 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# может быть ещё лучше, чем в других языках. Взгляните на следующее предложение по улучшению синтаксиса. Сколько всего красивого можно сделать…
Но я придерживаюсь той позиции, что 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.

В данном случае контроль ветвлений как раз и говорит: есть ветка, в которой переменная не будет инициализирована. А NRE можно получить только для reference-типов (и это все равно инициализация).

Вообще-то метод 'Unwrap' вызовется, если 'GetPoint' вернёт не 'null' и переменные 'x', 'y' получат свои значения. Просто контроль ветвлений не может сейчас этого понимать. Против NRE я совершенно ничего не имею, пример ниже прекрасно cкомпилируется и cгенерирует исключение в случае '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}");
Вообще-то метод 'Unwrap' вызовется, если

Я говорил о вполне конкретном коде:


var p = GetPoint()? { X to var x, Y to var y };

Здесь тоже произойдёт распаковка и инициализация переменных, если 'GetPoint()' вернёт не 'null'.

А если вернет null — не произойдет. О чем и речь.

Очевидно! И компилятор не понял, что мы используем переменные именно в той ветке, куда попадём только в случае не 'null'-значения.

В этом коде нет такой ветки.

Я, конечно, понимаю, что компилятору тяжело разобрать подобную ситуацию (и можно придумать её усложнения), но 'x', 'y' в данном конкретном случае будут однозначно проинициализированы в момент использования.

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}");
Справедливости ради отмечу, что опрераторы 'out' + 'var' могут объявить переменную x, даже если они очевидным образом не выполняются.
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' не является чем-то из рядя вон выходящим.

Эм. Инициализация переменной оператором, который не выполняется — это не что-то из ряда вон выходящее? Ну-ну. Спасибо, нет.

Эм… Объявление операторами 'out' + 'var' переменной, даже если они как бы и не выполняются… Не знаю, как вам, а мне нравится.

IModel GetModel() => null;
GetModel()?.To(out var m);
m = new AnyModel();

out и var — это не операторы. И да, объявление переменной меня не смущает, обратиться к этой переменной, если она не инициализирована, все равно нельзя.

… я еще понимаю, вы бы конвееры предложили, вот чего правда не хватает.

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

… и эти люди рассказывают нам про паттерн-матчинг.

Уж не обобщайте так, я не рассказываю про тот PM, каким вы его привыкли видеть. Если бы я знал его раньше, таким же и как вы, то, вероятно, принял бы моногие нестыковки как данность. Именно свежий взгляд позволяет увидеть всё с другой стороны.

На C# я пишу довольно много и доводилось делать непростые вещи… Если вам очень интересно, то можете полистать примеры кода.

Моя главные ориентиры в программировании — красота, лаконичность и простота. Основные мерила — обобщённость и количество строк. Из двух одинаково обобщённых решений для меня наиболее привлекательно то, в котором меньше строк.

Предлагаемая стандартная реализация PM будет мешать мне писать bodied-методы и решать некоторые задачи меньшим числом строк кода, поэтому у меня есть большое желание её переработать для большей лаконичности.

Конечно, прекрасно понимаю, что есть некоторые breaking changes, поэтому шансы что-то изменить призрачны, но, по крайней мере, я пробую свои мысли кому-то донести.
Уж не обобщайте так, я не рассказываю про тот PM, каким вы его привыкли видеть.

А зачем вы рассказываете про что-то свое, а потом называете словами, которыми другие люди называют совсем другое?


Моя главные ориентиры в программировании — красота, лаконичность и простота.

Два из трех субъективны.


Из двух одинаково обобщённых решений для меня наиболее привлекательно то, в котором меньше строк.

Я так и понял, на читаемость вам наплевать.


Предлагаемая стандартная реализация PM будет мешать мне писать bodied-методы и решать некоторые задачи меньшим числом строк кода,

Она не будет мешать вам делать ничего, что вы уже можете делать сейчас.

Она не будет мешать вам делать ничего, что вы уже можете делать сейчас.

Увы, вероятно, она закроет для меня такой синтаксис

GetModel() { AnyProp0 = "abc" }.AttachModel();
Не доступен, но очень хотелось бы.

Многие проблемы прекрасно решаются и без 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' в ином значении. Однозначно, это можно оставить как есть.

Суть в другом — в расширении значения 'var' и 'is var', без этого можно обойтись.
В вашем примере вообще ничего переписывать не нужно

Стоп-стоп. Вы говорили, что "Многие проблемы прекрасно решаются и без PM с помощью extension methods". Ну вот, решайте.


Однозначно, это можно оставить как есть.

Без паттерн-матчинга? Невозможно.


Суть в другом — в расширении значения 'var' и 'is var', без этого можно обойтись.

Только ценой ввода нового ключевого слова.

Как же невозможно? ) Старый добрый if-else да 'is', 'as' в помощь или даже elvis оператор '? :'.

Да. Лучше ввести новое ключевое слово, чем расширять смысл уже имеющихся 'var', 'is'.
Как же невозможно? ) Старый добрый if-else да 'is', 'as' в помощь или даже elvis оператор '? :'.

Во-первых, это не экстеншн-методы, как вы обещали. Во-вторых, это как раз с потерей читаемости, о чем я сразу и сказал.


Да. Лучше ввести новое ключевое слово, чем расширять смысл уже имеющихся 'var', 'is'.

Кому лучше-то? Вам лучше, другим, как выяснилось, нет.

То что там предлагается — это не паттерн-матчинг. Это with из паскаля.
Вот и я про то же — про трактовку 'var'. Моё мнение такое, 'var' следует использовать толко для вывода типов без навешивания дополнительной контекстнозависимой логики. Тогда это будет ясно, чисто, красиво.

Так это вы навешиваете логику. В шарпе всегда можно было писать:


Func<string> getNull = () => null;
var foo = getNull()

Тут var переменная null. Вы почему-то предлагает контекст, котором var никогда не null, нарушая существующую интуицию.


Данный код ещё не в релизе, поэтому его можно ломать. Кстати, в процессе обсуждения образовалось и новое предложение по двустороннему матчингу, поэтому можете высказать и свои мысли на этот счёт.

Данный код в релизе, и его ломать нельзя. Вот например выдержка из релизной версии моего проекта:
image
Зачем вы его сломать хотите?


Почему-то считается, что 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.).

В любом случае, он не должен ломаться ни для каких случаев, даже самых редких.


Просто изучите новое предложение по улучшению синтаксиса языка. Вы можете найти в нём противоречия?
Полностью согласен с Яковом
Хорошо, можно считать, что у меня тоже шаблонное мышление C# разработчика, поэтому я защищаю прежде всего видение с классической стороны и предлагаю синтаксис, который гармонично вписывается и развивает язык, открывая путь новым функциям.

Это ваше право так считать, что код не должен ломаться. Однако лично я согласен на некоторые 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' — это только для вывода типа и не более. Без расширения его смысла можно прекрасно и гармонично обойтись, кроме того это позволит реализовать двусторонний матчинг.

Какое расширение вы тут видите?


var x = default(string);

Вы внезапно навешиваете на var еще и смысл "проверяет, что не null". Вот как раз в случае выше var — это просто вывод типа. А у вас еще и проверка на null. Прекрасно, что у вас такое видение, но нужно уметь и других слушать, а не только затыкать уши "лалала я вас не слышу лалала вас не существует".

Это я навешиваю на 'var' проверку на 'null' !? Да вы шутите ??? :))
Вы меня слышите? На 'null' проверяет оператор 'is', а не 'var'.
Может, и это будет проверено оператором 'is', а не 'var'.

Ээээ… вы вообще в сгенеренный код смотреть пробовали? if (a is var b) return; превращается в if (true) return; (это в дебаге, в релизе, подозреваю, вообще весь код выкнется).

Я ж вам говорю про идеальный случай, а не про текущий релиз, который и пытаюсь оспорить.

А зачем проверять, что b может быть null? Это совершенно избыточный код, даже в идеальном случае.

Как же вы всё не поймёте меня… Если мне достаточно проверки на 'null', то я не хочу указывать тип явно и подключать нэймспейс. Просто хочу чтобы компилятор вывел тип (но мне важна эта 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 != nullcase, соответственно, будет case var m when m != null).

Извините, скажу прямо, для меня это костыльчик.

Это консистентно с поведением var в других местах.

В альтернативном подходе тоже всё консистентно.

Это каком конкретно?

Не, не консистентно. var x — это декларация переменной. Если она будет жестко non-nulllable, это не будет совпадать с поведением var x в out var x, и, тем самым, неконсистентно.


Да, я знаю, что is Type x неконсистентно с out Type x, но (а) вторая форма не используется и (б) is Type x консистентно с is Type.

… надо сказать, я впечатлен терпением LDT.

Там лишь несколько человек напрямую из LDT.

Раз вас всё устраивает, то добавят вам ещё один способ объявления переменных для ещё большей «консистентности»
'{ } m'.

А это не способ объявления переменной, это паттерн. И, если я правильно помню, он как раз матчит только non-null.

if (GetModel() is {} m) WriteLine($"It is model {m}");
else WriteLine("It is null");

Тоже костылек?
Именно. Новый способ объявления переменной в языке.
По-моему вы просто не понимаете смысла этой фичи, и поэтому вам не нравится то, что она в принципе в язык «проникла».

Стоит подумать, почему так получилось. Обычно рассуждения в стиле «они все идиоты, сделали такую хрень» означают то, что проблема на другой стороне.
Вот вы можете мне объяснить, почему способ объявления переменной оказывает прямой эффект на поведение pattern matching? Я думаю, что без этого вполне можно обойтись, как обходятся F# и Scala, например.

Вот вполне рабочая модель подобного подхода
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.
Вообще-то это вполне себе рабочая модель, которая прекрасно иллюстирирует, как работает матчинг по значениям и по типам, и в чём между ними разница. Конечно, модель не нужно понимать буквально, поскольку она реализована в жёстких рамках языка (C# не позволяет расширять синтаксис), но матчит значения и типы она исправно.

… потому что это не способ объявления переменной?

называйте, как захотите.

лично моё мнение, можно было бы реализовать pattern matching в C# более чисто и очевидно, чем это уже сделано сейчас.
Да, я могу использовать 'is object' только для проверки

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' к уже имеющимся 'IModel m' и 'var m'.

Вам уже ответили, что {} m это частный случай общего паттерна, который как раз дает оператор матча на не нулл бесплатно. И да, в языке ДОЛЖЕН быть оператор матчинга во всех случаях, даже если это выглядит как "еще один способ декларации переменной". Я тоже был одно время против неё, но LDM привели убедительные доводы, почему так должно быть. Просто игнорируя то, что вам говорят и продолжая твердить одно и то же вы ничего не добьетесь.

И почему так должно быть? Как вы понимаете доводы LDM?
На null оператор `is` проверяет, но только старый оператор, `value is Type`. Новый оператор, `value is pattern`, доверяет все проверки паттерну, а паттерн уже сам решает, что проверять и какие переменные создавать.

Паттерн `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, который матчит не нулл точно так же, если лень писать полтора символа идентификатора.

> a is Type b все так же матчит только ненулл.

Так тут и есть неконсистентность. null является значением типа Type (по факту, в системе типов шарпа), но при этом при проверке null is Type = false. Type при объявлении переменной — это nullable type, а Type в is — nonnullable Type. В зависимости от контекста это разные Type, вот в чем проблема. Чтобы все было консистентно, надо либо чтобы референс типы были nonnullable, либо чтобы null is Type = true, то есть null должно входить в тип либо не входить в него в обоих случаях. Либо надо вводить понятие, аналогичное конструктору, и привязывать семантику Type внутри паттерна к нему. Как бы внутри паттерна это не тип, а «конструктор» (что-то подобное) типа, потому логично, что они могут вести себя поразному.

Вроде как в C#8 станет повеселее с nullability.

Ну да, то, что null является элементов любого типа это проблема… Сразу появляются подобные костыли, но это уже привычный костыль, можно скажать, родной для шарпа.

Не вижу никакого "разбора", снова набор каких-то случайных утверждений.

Присмотритесь повнимательнее, вникните. Утверждения вовсе не случайны.

Если же вы не улавливаете взаимосвязи, то, к сожалению, ничем не могу помочь.

А зачем мне это, особенно с тем учетом, что вы приводите ссылку на хабре, но даже не утруждаете себя статьей для обсуждения?


Спасибо, нет.

По правде говоря, я уже устал обсуждать эту тему, поскольку обсуждалась она не только тут. В английском варианте собраны финальные аргументы, и люди, которые приложили некоторые усилия, смогли уловить их смысл.

Если устали обсуждать, то не надо было ее заново поднимать, публикуя ссылку.

Ссылка для информации и просто для тех людей, кому интересна тема.

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

… а на мнение других людей вам более-менее все равно. Ну да, ну да.

Ко мнениям окружающих я прислушиваюсь и при достаточной аргументации готов даже поменять своё личное.

К сожалению, убедительных аргументов в пользу того, что 'is var' и 'case var' для чего-то должны возвращать всегда только 'true' мне найти не удалось, хотя обратных доводов могу привести немало.

Вовсе не утверждаю, что теперь нужно вносить breaking changes, меня больше интересует, как мог бы выглядеть язык в идеальном случае. Ну, и то, что можно теперь использовать методы расширения 'Is' с чистой и предсказуемой имплементацией в случае необходимости. Кстати, для подхода с методом 'Is' можно также получить ряд любопытных обобщений, которых нет сейчас в языке.
Вовсе не утверждаю, что теперь нужно вносить breaking changes, меня больше интересует, как мог бы выглядеть язык в идеальном случае.

Никак. Не существует идеального случая для языка общего назначения.


Ну, и то, что можно теперь использовать методы расширения 'Is' с чистой и предсказуемой имплементацией в случае необходимости.

… в то время, как для большей части (а в моей личной практике — для всех) случаев достаточно использовать оператор is. Ну да, ну да.

… в то время, как для большей части (а в моей личной практике — для всех) случаев достаточно использовать оператор is. Ну да, ну да.

Как же вы отличаете те случаи, где можно было бы ещё применить оператор 'is', если даже не знаете про какие конкретно обобщения я говорю?

Коечно, до того, как ввели, например, pattern-matching, большинству разработчиков хватало и базовых возможностей 'is'.
Как же вы отличаете те случаи, где можно было бы ещё применить оператор 'is',

Я беру те сценарии, для которых применим ваш метод-расширение, и рассматриваю их.

Можете рассмотреть и такой сценарий, где применение метода-расширения более оптимально, чем родного PM, имплементация которого содержит подводный камень в виде упаковок для типов-значений, а также работает лишь с константными выражениями.

referencesource.microsoft.com/#mscorlib/system/collections/generic/equalitycomparer.cs

… а зачем вообще писать smth is 0, когда можно написать smth == 0?


Более того, вы утверждаете, что ваше решение "более оптимально", но по какому критерию оптимальности? Даже если рассмотреть банальную производительность — у вас есть конкретные бенчмарки, которые бы показывали, что две операции боксинга дороже, чем три статических вызова и один виртуальный?

Раз уж язык позволяет стандартными средствами написать 'i is 0', то почему бы и нет? Мне, например, больше нравится писать '==' в контексте арифметических выражений, а в логических использовать 'is', хотя они и взаимозаменяемы в некоторых случаях.

И да, интуитивно я предполагаю, что компилятор умный и развернёт '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
}

В каких случаях вызовется первое, а в каких — второе?


Да и правостороннее присваивание хорошо подходит для такого случая

Вот только оно вводит общую переменную, видную во всех кейсах, а зачем она мне?

Не понимаю, что тут сложного… case var x будет срабатывать всегда, когда метод возвращает не null, в случае null сработает default var x.

Наоборот удобно, если мне нужно по особому обрабатывать 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."

Ха-ха-ха, нашли спамера. Увы, ваши утверждения насчёт is var / case var обоснованы ещё меньше.

Так никогда не пробовали написать?
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 ничего толкового не скажу

Ну вот и не высказывайтесь по поводу этого поведения тогда.

Ну вот и не высказывайтесь по поводу этого поведения тогда.
Захочу — буду высказываться, не захочу — не буду. Это мне самому решать.

… и вы еще удивляетесь, почему — как вы говорите — вас забанили.

Вовсе я не удивляюсь, просто делаю для себя выводы…
Я правильно понимаю, что вы удалились из гитхаба, и вместе с этим потерлись все обсуждения? Все ссылки сейчас 404.
История такая: исходный аккаунт сначала забанили лишь в репозитории, но поскольку я всё равно продолжал отвечать на связанные вопросы людей, используя уже другие аккаунты (не скрывая своей личности), модератры нажаловались в поддержку гитхаба, дабы были приняты меры против неугомонного «спамера».

Но причины на самом деле во многом политические — просто критика в некотором роде подрывает авторитет разработчиков языка и «мешает» им работать. Если бы критические комментарии и аргументы были очевидно слабы, то почему бы их просто не проигнорировать? Зачем-то понадобилось блокировать человека… )
Понятно, куча людей осталось без комментариев потому что кому-то захотелось заспамить LDM. Браво.
Ха-ха… «Заспамить»… Спам — это когда пишут рекламные сообщеия либо не связанные с основной темой обсуждения (оффтоп), но никак не аргументированная критика.

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

Ну, под определения спама или оффтопа мои комментарии уж точно не попадают: они тесно связаны с темой обсуждения, нейтральны, содержат лишь технические детали. Более того, на них никто не обязан отвечать…
Ну, под определения спама или оффтопа мои комментарии уж точно не попадают: они тесно связаны с темой обсуждения, нейтральны, содержат лишь технические детали.

Я же говорю: это вы так считаете.


Более того, на них никто не обязан отвечать…

На спам тоже никто не обязан отвечать.

Я же говорю: это вы так считаете.
Считаю.

На спам тоже никто не обязан отвечать.
Удивительно много людей отвечало на мой «спам».
Удивительно много людей отвечало на мой «спам».

Удивительно много людей отвечает на спам в почте.

Но отчего же вы до сих пор отвечаете на мой «спам» и выражаете своё мнение на его счёт? Может быть, это изощерённый спам особого рода, который заслуживает некоторого внимания?.. А я коварный и тёмный программист, который сеет очень плохие идеи в неокрепших умах юных разработчиков и соблазняяет более опытных переметнуться на его тёмную сторону? )))

Всё проще. Нет у меня подобных манихейских замашек, я всего лишь делюсь личным опытом.
Но отчего же вы до сих пор отвечаете на мой «спам» и выражаете своё мнение на его счёт?

Потому что мне так хочется.


Нет у меня подобных манихейских замашек, я всего лишь делюсь личным опытом.

Ничего удивительного в том, что кто-то считает, что этот опыт не интересен в определенном сообществе.

Ну, вот и другим хочется.

Да? Но заинтересованные люди нашлись.
Там не было аргументированной критики. Вы просто повторяли одну шарманку «так писать плохо» и полностью игнорировали то, что вам пишут ответ.

Далее, создавать кучу аккаунтов и писать на личные скрытые почты 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 p выглядит очень даже ничего…

Вот мы и пришли к "на мой взгляд". А как же объективные аргументы?


(ну и да, проблему default var тоже уже разбирали)

Проблем с default var не больше, чем с case var.
Проблем с default var не больше, чем с case var.

И где у case var проблема с конфликтом default var и case var?

Так в том и дело, зачем было вообще создавать этот конфликт и нарушать симетрию, если можно было сразу всё сделать красиво? Вы мне лучше на этот вопрос ответьте, а то ходите по кругу: is var всегда true, потомучто case var всегда true, а 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 обозначает декларацию переменной (тип которой будет выведен).


Во-вторых, вывод типа никуда не делся.

default var
case var
case object
Так себе аргумент с визуальной сложностью.

Во-вторых, вывод типа никуда не делся.
В-третьих, добавилось неочевидное поведение с null.
Так себе аргумент с визуальной сложностью.

Кому как. Это, очевидно, визуально сложнее, чем


case X _:
case var x:

В-третьих, добавилось неочевидное поведение с null.

Значения ключевого слова это, однако, никак не поменяло.

то закономерно ожидаю аналогично симетрии для случаев

Почему ожидаете?

Потому что это интуитивно, var для меня означает вывод типа компилятором и не больше.

Ключевое слово "для вас". Мы уже неоднократно видели, что ваша интуиция далеко не всегда верна.

Потому что это интуитивно, var для меня означает вывод типа компилятором и не больше.

А для меня не означает. Как быть?

Да пользуйтесь, чем вам хочется. Я что, склоняю вас к чему-то? Все эти примеры предназначены в основном для тех, кто предпочитает использовать var в классическом значении вывода типа.
Я что, склоняю вас к чему-то?

Ну да, вы пытаетесь выдать свое "мне интуитивно" за объективный аргумент.

как вывод формул в математике.

При этом в математике, скажем, активно используется правило подстановки, которое в вашем коде невозможно — но вас это не смущает.

Вы лучше на примере поясните, где и что нужно подставить, а в моём коде нельзя так сделать…

Серьезно?


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#, а в виде уравнения.

А для меня это и есть то самое начало, за которое мне нравится программирование, поэтому к ней стремлюсь в любом языке.

Да вы регулярно говорите, что вы к нему стремитесь, только ваш код этому противоречит. Видимо вы под "математической красотой" — снова — понимаете что-то свое.

Мой код соответствует моим понятиям о математической красоте.

Это ровно то, о чем я вам и говорил с самого начала.

Скажите пожалуйста, каким способом вы предлагаете матчить произвольное значение, и приведите пример, как это будет выглядеть в случае подпаттернов.

Я про рекурсивные паттерны, то есть SomeRecord(var x, var y, var z). Как вы предлагаете записать такой паттерн?
Матчить сложные паттерны можно уже сейчас (примеры см. тут).

Самое сомнительное решение дизайнеров языка — это неочевидное смешение матчинга по типу и по значению (например, в F# они разделены). Разбор имплементации здесь.
неочевидное смешение матчинга по типу и по значению (например, в F# они разделены)

Ээээ, что? A вот это:


match shape with
| Square(2, 2) -> 1
| Circle(4, 5) -> 0

это по типу или по значению?

Это грамотная композиция, а не смешение. Мы берём фичи A и B, комбинируем и получаем C=AB, а не С=XB, где X это как A, но уже не совсем в данном контексте.

Как по вашему должен работать такой псевдокод
match shape with
| Square(2, 2) -> 1
| Circle(4, 5) -> 0
| var(0, 0) -> 3 // Shape(0, 0) -> 3

где базовый Shape (выведенный тип для var) поддерживает деконструкцию?
где базовый Shape (выведенный тип для var) поддерживает деконструкцию?

Погодите, это вы предлагаете, чтобы var что-то там выводил и поддерживал, вы и объясняйте. В текущей реализации то, что вы написали просто не скомпилируется, это некорректный паттерн.

Я указал выше, что это псевдокод. var означает только вывод типа компилятром из контекста в классическом понимании.

В F# нет var для вывода типа, для этого используется идентификатор без указания типа вовсе:


let t q ->
match q with
| None -> true
| Some(_) -> false

q выводится как option (без ограничений), t выводится как option -> bool.

Это грамотная композиция, а не смешение.

… и как вы объективно отделяете одно от другого?


Обратите внимание, раньше вы говорили, что они разделены, теперь вы говорите, что они скомпонованы. А часа не прошло.


Как по вашему должен работать такой псевдокод

Никак, в 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",
}

?
Извините, но Вы — наркоман.
Нет, я не считаю это лучшим синтаксисом, он просто доступен мне уже сейчас в качестве альтернативы. Я только считаю, что is var / case var реализованы некрасиво без явной на то необходимости, вот и всё.
реализованы некрасиво

Очень объективный аргумент, да.

Я уже привёл аргументы выше, и не раз.

Ни один из них так и не оказался объективным.

Хорошо.
Druu
И да, я считаю, что
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
);

Выглядит сейчас получше, чем обычный switch.
модератры нажаловались в поддержку гитхаба, дабы были приняты меры против неугомонного «спамера».

И правильно сделали.


Если бы критические комментарии и аргументы были очевидно слабы, то почему бы их просто не проигнорировать?

Потому что они мешают конструктивной дискуссии.

Не сомневался в вашей позиции! :)

Вот только сам я и начинал те дискуссии, из-за которых меня изначально решили заблокировать. И, замечу, подавляющее большинство моих комментариев были всего лишь аргументированными ответами на сообщения других пользователей, никого не призывал вливаться в дискуссии.
Вот только сам я и начинал те дискуссии, из-за которых меня изначально решили заблокировать.

И что? Это как-то делает их более полезными для сообщества?


подавляющее большинство моих комментариев были всего лишь аргументированными ответами на сообщения других пользователей

Во-первых, даже аргументированный ответ не обязательно является конструктивным.


Во-вторых, вы меня извините, но я уже достаточно долго наблюдаю за вашим уровнем аргументации, и по моим наблюдениям он практически всегда сводится к "мне так больше нравится".


Ну а в-третьих, поскольку благодаря вашему поведению мы уже не увидим этих комментариев, никакого способа подтвердить ваши слова у вас нет.

Ну а в-третьих, поскольку благодаря вашему поведению мы уже не увидим этих комментариев, никакого способа подтвердить ваши слова у вас нет.

Это трагедия — никто не поверит мне… ))

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

Кроме того, кастомные решения во многом я применяю для своих нужд, они меня устраивают даже больше встроенных, а в статьях просто делюсь наработками с другими людьми, и, судя по голосованиям и просмотрам, эти исследования интересны также другим разработчикам. Вот и всё.

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

Свою функцию для вас, а не для сообщества.


на текущий момент существенных недостатков в рассмотренных решениях не вижу

Собственно, в этом и проблема.

Собственно, в этом и проблема.
Ну, вы так и не предоставили убедительных для меня аргументов, обнаруживающих существенные недостатки, из-за которых мне стоило бы отказаться от своих кастомных решений.

Учитывая, что степень убедительности аргументов определяете вы сам, и вы не предоставляете никаких формальных способов это оценить — это неудивительно.


Впрочем, у меня и не было цели убедить вас.

И меня не было цели убедить вас или кого бы то ни было ещё.

Человек, ознакомившись с материалами, сам поймёт, нужно ему это или не нужно. :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории