Pull to refresh

Comments 69

Теперь в C# 7 добавилась поддержка сеттеров, геттеров, конструкторов и деструкторов:

Вы имели ввиду финализатор?
Да, лучше назвать финализатором. Но с другой стороны, именно метод ~Class() в официальной документации msdn называется деструктор (на русском): https://msdn.microsoft.com/ru-ru/library/66x5fx1b.aspx
А финализатором тогда можно назвать именно метод
protected override void Finalize()

Некоторая путаница с переводами понятий.
а толку от деструкторов, если они вызываются через неопределенные отрезки времени после потери последней ссылки (в сборщике мусора), а не сразу.
Да никто их и не рекомендует использовать в продакшене, если честно. Есть Dispose(), который вызывается именно тогда, когда надо.
они бы использовались, если бы деструктор вызывался бы сразу, а не в сборщике мусора.
с Dispose вот только using { } для меня избыточный код, поэтому лучше вообще без него.
поэтому лучше вообще без него.

это как? с try… finally

… а типизированных на этапе компиляции шаблонов так и нет :(
И ведь под капотом оно всё равно создаёт специфичный для каждого каждого использованного дженерик-параметра код, но синтаксиса для того, чтобы явно определять поведение для некоторых типов так и нет.
Да, и пока не планируется особо. Наверное, не хотят делать второй C++ все-таки.
Generic-и разворачиваются в общий код для дженерик-параметров в виде объектов, и специфичный код для каждого value-типа.
Объявление 5 конкретных типов «List, List
Какой приятный синтаксис! Когда читаю про нововведения очередной версии C#, всегда радуюсь — все так просто и естественно, именно так как ожидаешь (хотя сам пишу в основном на С++).
Полностью согласен! Возможно, изменений в каждый релиз не много, но зато все они более-менее проверены.

Приятный? Паттерн матчинг выглядит, мягко скажем, уродливо.
Сравните с f#


type Color =
    | Red = 0
    | Green = 1
    | Blue = 2

let printColorName (color:Color) =
    match color with
    | Color.Red -> printfn "Red"
    | Color.Green -> printfn "Green"
    | Color.Blue -> printfn "Blue"
    | _ -> ()

Да и поддерживается он очень ограничено. Опять же, сравните с F#

Был и мне кажется что switch это самый не удобный оператор притянутый их с++ лучше бы его упростили то примерно такого


switch(shape)
{
    Circle c:
        WriteLine($"круг с радиусом {c.Radius}");
    Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} квадрат");
    Rectangle r:
        WriteLine($"{r.Length} x {r.Height} прямоугольник");
    null:
        throw new ArgumentNullException(nameof(shape));
    default:
        WriteLine("<неизвестная фигура>");
}
Когда давным-давно после паскаля я стал изучать си, то все эти case для каждой ветки тоже казались громоздкими. А теперь вот думаю что это грамотное решение: каждая ветка начинается с конкретного ключевого слова, а не с фиг знает чего. Особенно с учетом того, что в аргументе case в принципе можно писать всякие навороченные выражения, важно четко выделять границы между аргументом case и телом блока.
В Rust'е эту проблему просто решают правильным форматированием веток match'a и не плачут… наверное можено все-таки без case обойтись.
Чем меньше возможностей выстрелить в ногу, тем лучше.
Ключевое слово «case» должно быть. А вот «break» напрягает.
Тоже напрягает. Мне кажется логичным, что если 2 case подряд — то к одному блоку, а если между ними есть код, то каждый относится к своему. Им, видимо, показалось логичным избежать путаницы с ++, где без break можно уйти в соседнюю ветку, хотя в # так сделать нельзя и break выглядит излишним.
«Сопоставление» выглядит интересным разве что в case случае. Есть ещё полезные примеры на эту тему?
Пока паттерн матчинг полезен действительно, скорее в switch. В следующих релизах должны добавить еще мета, где можно будет его использовать.

думаю нормальный синтаксис сопоставления образцом может заменить и if и switch. Т.е. они станут как бы не нужны, потому что можно будет писать короче


match {
  patern1: action1;
  patern2: action2;
  default: actionDefault;
}
out по мне не надо было трогать. Да, удобно, но не скажется ли понимании кода

По мне так это давно напрашивалось. И локальные функции это отлично. Вот бы еще побольше вывода типов входных и выходных значений и получился бы F# с синтаксом C#.

UFO just landed and posted this here
Скачайте последнюю версию — Preview 4, должно заработать — кроме тех случаев, что оговорены отдельно (не все фичи пока поддерживаются в полном объеме).

Почему не добавили автоматический вывод типов параметров? Боятся конкурировать с F#?

Думаю, в некотором роде да, потому что иначе C# станет слишком похож на F#, вряд ли это и есть стратегия Microsoft.
Я не программист на c#, однако имхо из вполне чистого и красивого синтаксиса делают сложно читаемое, сложно понимаемое и сложно парсерное нечто :/
Я программист на с# и это просто ужасно :( назвали бы этот кошмар c## или как-то еще, чтобы людей с толку не сбивать.
… и сложно парсерное нечто
парсер кода уже написан — Roslyn, бери и пользуйся. Это не проблема.
Да, мне тоже не по душе большое количество синтаксического сахара, но новые примочки кажутся вполне удобными. Код становится более лаконичным и понимаемым, а писать в старом стиле никто не запрещает.

Что плохо, так это возможность обфускации кода, например, объявление переменных в виде:
if (0 is var start && 100 is var count) ...
а вас за уши заставляют это все использовать? всему этому сахару есть ручной аналог.
Не совсем так. Например, в блоке «catch» нет ручного аналога для фильтра «when».
Можно, конечно, вместо
catch (...) when (condition)
{
    ...
}
написать
catch (...)
{
    if (condition)
    {
        ...
    }
    else throw;
}
, но семантика тут будет другая. В первом случае не будет осуществляться разворот стека.
То есть когда «when» используется в «case» — это сахар, а в «catch» — не сахар? Нелогично же!
разница в том во что разворачивается конструкция.
catch when это exception filters, фича низкого уровня ранее недоступная C#.(она есть в VB.NET)
using это сахар, он разворачивается в try finally Dispose.
Я знаю, во что она разворачивается. Я просто пишу об отсутствии логичности: в одном случае это низкоуровневая фича, в другом — синтаксический сахар.
я только что описал разницу, вы все еще не видите логики?
без сахара можно легко жить и добиться того же.
а вот без фичи, повторить тоже самое уже не выйдет.
Представляю себе количество разностильного кода — кто вручную, кто с сахаром, кто перемешивает. В целом сахар неплохой, но вот делать для ctor/dtor лямбды никакого смысла не вижу — экономия копеечная.
И всё же это не лямбды. Просто короткая запись с использованием лямбда-оператора.
Само собой — оно только внешне похоже. И все же сокращение только за счет фигурных скобок мне не нравится — раньше свойства и методы визуально легко разделялись. Теперь же это усложнилось, вдобавок будут короткие методы. которые в одну строку, и есть длинные со скобками. В style guide'ах добавятся пункты, чтобы в ревью не возникали священные войны. В общем, пространства для вкусовщины теперь больше.
Шаблоны (patterns) есть, кортежи есть, но шаблонов кортежей нет?
Шаблоны наиболее полезны для работы с кортежами и подобными им объектами (как case class в scala). Странно, если это не реализовано.
Ваши пожелания может и разумны, но тогда к ним я хочу унификацию, как в Prolog! )
UFO just landed and posted this here
Да, ждем примеры от команды Microsoft, пока с таким поиграться не получится — не поддерживается.

Объясните мне кто понял про кортежи System.ValueTuple.


С одной стороны там Item1, Item2, также как в обычном System.Tuple, с другой стороны можно дать осмысленное имя полям — Id, Name.


Вот если я напишу публичную функцию


public (long Id, string Name) GetDto(){ return (1, "1");}

А потом её зареференсю из другой сборки, то что я увижу — Item1, Item2 или же Id, Name ?


Моё мнение, что я должен увидеть Item1, Item2, потому что System.ValueTuple это struct и он не изменяем — но как тогда сохраняется семантика, что первый элемент кортежа назван Id, а второй — Name?
Кто нибудь разбирался, как это под капотом работает?

В библиотеке System.ValueType помимо самого System.ValueTuple есть System.Runtime.CompilerServices.TupleElementNamesAttribute, который и сохраняет имена элементов объектов типов System.ValueTuple.

То есть ваш код будет скомпилирован в что-то вроде этого:
[return: TupleElementNames(new[] { "Id", "Name" })]
public ValueTuple<long, string> GetDto(){ return (1, "1"); }
return new ValueTuple<long, string>(1, "1");
Конечно.
Лично мне очень не хватает возможности перегружать операторы для интерфейсов, да и вообще — для дженериков.
Почему я могу написать метод-расширение:
public static IData<T> Add<T1, T2>(this IData<T> lhs, IData<T> rhs)
но не могу:
public static IData<T> operator + (IData<T> lhs, IData<T> rhs)
?
Методы расширения могут только добавлять функционал и вы ничего не сломаете добавив его.
Однако операторы перегружаются и вы можете сломать написанный код который использует оператор.
Если же вы разрешите определять операторы только там где их нет, то расширения сломаются когда оператор определят в самом типе.
Не вижу проблемы. Если оператор не определён, то ищутся расширения.

Если же вы разрешите определять операторы только там где их нет, то расширения сломаются когда оператор определят в самом типе.

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

Недавно обсуждали в комментах, что C# давно уже движется в сторону забытого нынче Nemerle… высока вероятность, что где-нибудь через 3-4 версии у них будет паритет по фичам и примерно одинаковый синтаксис.

Nemerle на самом деле еще немного жив, и писать на нем одно удовольствие и переучиться с C# довольно просто.
Но вот использовать его как основной язык в проекте проблематично, по той причине, что нету для него внятных решений типа ReSharper.
Возможно, когда команда Jetbrains допилят проект Nitra и на нем уже построят следующую версию Nemerle, всем будет счастье.
(var first, var middle, var last) = LookupName(id1); // var внутри
По моему это просто чудесно!!! :) Наконец это появилось и в C#!
Ммм… Out переменные, кортежи, шаблоны. Хочу ))) А кто может сказать, когда намечается релиз 7 версии C#?
Люди добрые, объясните темному бедолаге зачем могут быть нужны локальные функции? Что они делают такое чего не покрывают лямбды?
Подозреваю, что это сахар для лямбд и есть. В целом, локальная функция воспринимается (по крайней мере, мной) как что-то более очевидное, чем лямбда — это синтаксически отличный от объявления переменной код.
По моему скромному мнению, здесь могут быть две причины: область применения и реализация на уровне фреймворка (компилятора, если будет угодно).

1)Область применения
Лямбда применяется, когда на вход требуется делегат типа Func, Action, Predicate, обработчик события и прочее. И нужно это, дабы не плодить миллионы однострочных хендлеров для миллионов входных типов, улучшить читабельность и концентрацию, чтобы не носится как бешеный по всему исходнику. Плюс позволяет не создавать отдельный метод, если он нужен только один раз.

Локальная функция имеет те же преимущества, но позволяет обернуть любой код. Например, если нужно посчитать несколько значений по одному и тому же алгоритму.

2)Могу ошибаться, но логично предположить, что реализация у них разная.
Лямбда аллоцируется в памяти, а локалка, скорее всего, работает по принципу inline-функций и разворачивается на этапе компиляции.
Вряд ли в общем случае получится локальную функцию заинлайнить (например: реккурсия). Но скорее всего можно будет сэкономить на аллокации замыкающего класса и сгенерировать функцию принимающую структуру с параметрами.
Делать лямбду из нескольких выражений — все-таки не очень хороший тон, лучше использовать новые локальные функции.
Локальная функция — хороший способ не плодить сущности в виде нового класса.

Как по мне локальные функции будет очень удобно использовать для реализации какой-либо рекурсии (собственно что и продемонстрировано в примере). Часто есть какая-то оборачивающая public функция, которая внутри себя инициализирует какую-то структуру и передает ее уже основной внутренней рекурсивной функции. Эту структуру можно передавать с помощью аргументов, а можно с помощью полей класса. Оба эти решения не очень хорошо выглядят: в первом случае придется каждый раз передавать эту структуру через рекурсивные вызовы (избыточность и избыточное потребление памяти стека), а в другом — добавляется побочный эффект. Локальные функции как раз и решают обе этих проблемы: аргументы не нужно передавать через сигнатуры, но с другой стороны все объекты являются локальными.

У лямбд более громоздкий синтаксис. Требуется явно указывать тип делегата, из-за чего нельзя воспользоваться анонимным типом. Кроме того, если лямбда рекурсивна, требуется двойная инициализация переменной.


Пример из статьи на C# 6 выглядит так:


public int Fibonacci(int x)
{
    if (x < 0) throw new ArgumentException("Не надо негатива!", nameof(x));

    Func<int, Tuple<int, int>> Fib = null;
    Fib = i =>
    {
        if (i == 0) return Tuple.Create(1, 0);
        var p = Fib(i - 1);
        return Tuple.Create(p.Item1 + p.Item2, p.Item1);
    };

    return Fib(x).Item1;
}
Sign up to leave a comment.

Articles