Pull to refresh

Comments 71

1 - Enum должны быть быстрыми. Они для этого и целочисленные

2 - В Java Generic это синтаксический сахар языка, с точки зрения виртуальной машины IList<ClassA> и IList<ClassB> это одно и то же из-за Type Erasure (https://docs.oracle.com/javase/tutorial/java/generics/erasure.html). В .NET Generic реализованы на уровне виртуальной машины специальными опкодами.

3 - они .NET 7 появятся

4 - в C# для этих целей сделаны делегаты

Ну т.е. не нужно на C# писать также как пишут на Java, это отдельный язык и несмотря на его похожесть - это всё же не то же самое

1 - Enum должны быть быстрыми. Они для этого и целочисленные

Вы это так формулируете, что может показаться, что в Java enum не быстрый. Что вы, кстати, вкладываете в это понятие?

Возможно тут речь про то что существует полно статей в которых не рекомендуется использовать enum под Android, дескать размер программы больше в разы а расход памяти больше на порядок по сравнению с public static final int

Дело не в скорости, а в p/Invoke. Изначально enum делался совместимым с C enum

Строго говоря нет причин не сделать как с потомками WaitHandle: при p/invoke они передаются как инкапсулируемые хэндлы.

  1. enum мог бы по умолчанию целочисленным. А по необходимости - любого другого типа - просто аналогом статического класса со статическими свойствами только для чтения (как во второй реализации) - чистой воды синтаксический сахар т.к. по сути таковым и является enum в Java. Вот в Kortlin есть разделение нумераторов - на обычные и классовые - просто синтаксическая удобная обёртка над классом. А вообще - всё это ещё со Scala пошло. Ну да - в сфере новых тенденций внедрения новых языковых фишек (в основном, кстати, стягиваемых уже с ЯП Kotlin) - вполне можно было бы и "enum class" перенять (как "record class" уже перетянули) - для удобного создания подобных статических классов-перечислений.

  2. Так то оно так - но в связи в введением в C# ковариантности можно было бы и реализовать такую совместимость при переопределениях, хотя бы при целенаправленном указании намеренья такого переопределения (тем или иным способом) - чисто ради соблюдения логики поддержки ковариантности типов. Было бы реально полезно. Но, подозреваю, что это сложный в реализации механизм для компилятора - в C# высокий уровень статического контроля типов при компиляции.

  3. Это Вы что имеете в виду? Ссылочный тип на функцию, что не делегат?

  4. У вас что-то сбилось в нумерации? в 4. пункте было про анонимные реализации интерфейсов в Java - они же - анонимные типы (классы) в C# . Тогда да - в C#, почему-то анонимные классы пока очень ущербны - не то, что в Kotlin - где их возможности велики, и где есть реализация интерфейсов. Очень ждём такую же фишку в C#, кстати, её поддержка в .NET (на уровне IL) уже давно есть - но задействовать без специальных ухищрений не удастся (хотя кое-какие готовые решения уже есть на эту тему на базе процессинговой системы PostSharp, например)

А вообще-то, правильно уже сравнивать C# не с Java (этот ЯП уже устарел) - а с Koltin - вот тут C# есть чему поучиться - и да он активно учится!

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

4 - Анонимные реализации интерфейсов потому и не нужны, что это антипаттерн. Просто нужно делать через события. В Java нет событий и делегатов, поэтому это делается через реализацию интерфейса.

Тут не сбитая нумерация, а просто это связанные между собой вещи. Делегаты в C# и анонимные реализации интерфейсов в Java созданы по сути для одной цели. Поэтому похожий функционал в них реализован просто разными средствами языка и виртуальной машины.

чтобы сделать Generic Math. Следствием этого будет поддержка этого функционала

Обобщённые статические перегрузки операторов в интерфейсах - это конечно круто будет - давно пора! Вот бы ещё перегрузку операторов через функции расширений сделали бы....

Анонимные интерфейсы в С# может и не особо нужны - а вот поддержка интерфейсов анонимными классами очень не помешала бы.

  1. Решение сам и написал. Перечисления в C# - это именно классические перечисления, просто константы на уровне компилятора. В Java это совершенно иная конструкция. Использование enum в Java стиле спорная вещь, по сути это перечисление, что возвращает данные совершенно иного типа. Ну и как бы слегка нарушает принцип единой ответственности.

  2. Ковариантность - это такой неплохой способ стрелять себе в ногу. Зачем? Пример приведён чисто академический. В реальной жизни ООП на таком уровне где-то используется? Лучше уж жить на фабриках и тогда ковариантность вообще не потребуется.

  1. Смотри что такое delegate в C#

  2. Собственно мне наоборот не нравится данная фича. Так как олпять же вносит энтропию в проект. Да, слегка уменьшает объёмы кода, но при этом в Java есть куча всего, что прям увеличивает объёмы кода в неимоверных масштабах. Ну и опять же, делегаты убирают кучу необходимых мест для использования анонимных реализаций интерфейсов.

Про делегаты написал ещё в статье, что их существование аннулирует необходимость функциональных интерфейсов:

В C# есть делегаты, поэтому надобность в подобном сахаре крайне сомнительна

Перечисления в C# — это именно классические перечисления, просто константы на уровне компилятора. В Java это совершенно иная конструкция. Использование enum в Java стиле спорная вещь, по сути это перечисление, что возвращает данные совершенно иного типа.

Дланьчело.
Перечисление это математическая абстракция, представление оного в виде числа — не более, чем его грубое приближение из-за ограниченности ресурсов на заре компьютеризации, а вовсе не "классика".


An enumeration is a complete, ordered listing of all the items in a collection. The term is commonly used in mathematics and computer science to refer to a listing of all of the elements of a set.

https://en.wikipedia.org/wiki/Enumeration


Никаких ограничений на внутреннюю структуру элементов перечисления вышеупомянутая абстракция не налагает.

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

Ну вроде все нормальные языки стараются избавиться от проверяемых исключений) А для непроверяемых это делать крайне странно IMHO

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

Ну вот например kotlin, почитайте там целая глава почему они отказались от проверяемых исключений. https://kotlinlang.ru/docs/exceptions.html

В swift, typescript тоже обошлись без них

Тут вся школа ФП-на-эффектах икнула. ZIO/CATS на скале к примеру.

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

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

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

Да. Только если это компилятор решает - это хорошо он ещё способен отследить все зависимости. А человек - не способен. Человек, в среднем, не способен отследить взаимодействия между 4-5 микросервисами "своей" кодовой базы. Пусть это делают машины

А Вы уверены, что в мире не бывает ничего кроме мелкосервисов? Что не бывает вызовов более чем 1-2 методов в парочке сторонних библиотек? Что их не нужно комбинировать?

По первому пункту посмотрите на Discriminated Unions в F#.

Это другое и похоже скорее на sealed classes в Java.

Эта история уже из немного другой оперы

1 пункт по делу - конечно enum как константы быстрые и простые по поведению, но данных в них не хватает.
Возможно это стоит решать не докруткой enum, а заведением честного типа-суммы специально для этих целей.
2 пункт имеет не очень большую практическую ценность, возможно явное указание даже лучше.
Но это на любителя.
А вот 3 и 4 пункты наоборот видятся слабостью Java
В c# очень рано появился хороший механизм делегатов (которые фактически и являются стандартизированным интерфейсом одного метода) и лямбды, по сути полностью исключив "интерфейс с одним методом" и сахар "реализации на месте".
Java пришлось догонять.

Чего еще можно добавить - неизменяемых переменных, аналога val в kotlin
В Java в замыкание можно положить только final переменную, что значительно улучшает понимание замыканий.

Спасибо за конструктивный комментарий!

По поводу enum. Можно навесить на значения атрибуты с дополнительными значениями и добавить extension-метод, который их извлекает.

Собственно, данный вариант описан в статье

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

По идее есть же Case Guards:


public readonly struct Point
{
    public Point(int x, int y) => (X, Y) = (x, y);

    public int X { get; }
    public int Y { get; }
}

static Point Transform(Point point) => point switch
{
    { X: 0, Y: 0 }                    => new Point(0, 0),
    { X: var x, Y: var y } when x < y => new Point(x + y, y),
    { X: var x, Y: var y } when x > y => new Point(x - y, y),
    { X: var x, Y: var y }            => new Point(2 * x, 2 * y),
};

То есть берёшь любой класс с произвольными полями и лепишь по нему нужные switch'и. Или я чего-то не понимаю?

У вас не тип-сумма в примере, хоть и pattern matching присутствует. Посмотрите примеры в Википедии https://en.m.wikipedia.org/wiki/Tagged_union

В Java enum - это тоже не тип сумма. Для этого там есть sealed classes. У enum в Java просто можно хранить связанные с его значениями данные, что иногда бывает удобно. Связанные со значениями enum, а не с типом, заметьте. Грубо говоря, enum в Java - это final-класс, который нельзя расширить, и несколько его именованных синглтонов. Почти как в примере реализации из статьи.

Грубо говоря, enum в Java - это final-класс, который нельзя расширить, и несколько его именованных синглтонов.

А вот и нет. Одна из фишек енумов из Java в том что можно объявить метод в енуме, но потом написать разные оверрайды в каждом из элементов.

А вот и да. Синтаксис оверрайда тот же самый.

Если речь о том, что нельзя оверрайдить final-класс, то для этого и были слова "грубо говоря".

https://onecompiler.com/jshell/3yg4cwmpc

class Enum {
  public static final Enum FOO = new Enum();
  public static final Enum BAR = new Enum() {
    public void say() {
      System.out.println("bar");
    }
  };
  
  public void say() {
    System.out.println("foo");
  }
}

Enum.FOO.say();
Enum.BAR.say();
foo
bar

Ну да, я именно про это. Что элемент енума есть не просто контейнер с данными, но потенциально ещё и логика. Паттерн strategy на них хорошо ложится например.

Отличная статья!
По поводу enum, можно использовать Extension Method чтобы сделать что-то подобное. Вот набросал простой пример:

void Main()
{
	var today = Week.Monday;
	today.ShouldIWorkToday().Dump();
}

public enum Week 
{
	Monday,
	Tuesday,
	Wednesday,
	Thurday,
	Friday,
	Saturday,
	Sunday
}

public static class WeekHelper 
{
	public static bool ShouldIWorkToday(this Week day) => day != Week.Saturday && day != Week.Sunday;
}

Спасибо! О расширениях было написано в статье

Точно, что-то я пролетел сразу!

UFO just landed and posted this here

В С# не хватает типа never, поэтому TS мне стал милее.

Зато в C# можно сделать тип "ни да, ни нет".

Вот уж чего не хватает C#, так это реализации IEquatable<> для enum.
Вместо этого приходится использовать костыль в виде EqualityComparer<>.Default.Equals (который, кстати, инлайнится, что не так уж и плохо).


Насчёт пункта 4: скоро в C# завезут file-local types:
https://github.com/dotnet/csharplang/issues/6011

Мне не хватает

1) наследование enum

2) деконструкторы анонимных обобщенных ограничений очень крутая фича для выдергивания на ходу нужных полей без создания структур

3) взаимодействие микросервисов по REST с помощью типизированных интерфейсов как wcf но лайтовее в миллиард раз

4) использование enum в обобщённых ограничений

4й пункт я как вы знаете протолкнул. 2й пункт признаюсь очень трудно протолкнуть. 3й пункт будет через несколько версий , над этом фичей работаем вместе с командой и MVP MSFT Suzuki, Yoshizaki, Kawai (это всё азиатские имена , не фирмы ^_^)

Обещаю что 3й пункт будет базой для создания взаимодействия микросервисов .net

взаимодействие микросервисов по REST с помощью типизированных интерфейсов как wcf но лайтовее в миллиард раз

А с какой радости это часть языка?


4й пункт я как вы знаете протолкнул.

Неа, не знаем. Где про это почитать?

По enum добавили в 7.3 Можете тут посмотреть https://dotnetfalcon.com/whats-new-in-c-7-3/ пункт "New generic type constraints"

По пункту 3: Часть из за того что уже есть всё что нужно для этого а именно serviceHost и httpclient и interfaces

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

По enum добавили в 7.3 Можете тут посмотреть https://dotnetfalcon.com/whats-new-in-c-7-3/ пункт "New generic type constraints"

По этой ссылке нет никакого упоминания, кто это "протолкнул". Где можно про этот процесс прочитать?


По пункту 3: Часть из за того что уже есть всё что нужно для этого а именно serviceHost и httpclient и interfaces

HttpClient — точно не часть языка. Как вы — и, главное, зачем — планируете делать в языке что-то настолько высокоуровневое и специфическое?

Честно говоря что что, а 2-й пункт никогда не вызывал вопросов. Потому что в отличие от перегрузки виртуального метода, интерфейс можно реализовать явно. Да, это +1 строчка, но имхо это несущественная мелочь.


class Foo : ICloneable
{
    object ICloneable.Clone() => Clone();
    public Foo Clone()
    {
        throw new NotImplementedException();
    }
}

Описывать enum, и здесь же - задавать некие значения - имхо, не в духе ООП языков. Даже взять ваш пример, некий парсер: в одном файле будет enum, как описание типа, в другом - код самого парсера, где тип используется. Какие символы надо трактовать как некий enum item - это задача и ответственность парсера. Читая его код придётся прыгать внутрь enum чтобы увидеть, какие именно символы соответствуют каждому пункту. А если правило усложнится, его описание может вообще перестать сводиться к строковому литералу. В общем случае, пункты enum могут в разных частях кода иметь разное соответствие каким-то ситуациям, значениям. Это зона ответственности кода, использующего тип. А вы в сам тип пытаетесь частично перенести логику кода, этот тип использующего. В общем случае, не на синтетически примерах, даёт ли это больше плюсов, чем минусов?

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

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

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

А мне в C# очень не хватает 10-летней поддержки LTS-релизов.

А в чём трудность? Написанное 10 лет назад не работает?

Написанное 10 лет назад не работает?

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

Кстати, обратной совместимости между рантаймами мне тоже не хватает.

А при чём здесь только шестой рантайм? Вроде как поддержка продукта в режиме LTS подразумевает наличие этого самого продукта, разве нет?

У меня приложения, написанные 15 лет назад даже на NetFX 2.0 прекрасно работают (речь идёт именно про работу, а не поддержку). Другое дело если бы в один прекрасный момент поддержку старого дотнета совсем выпилили из целевой среды, для которой это изначально писало. А так операционные системы прошли пяток мажорных релизов, а софт с тех времён ещё работает. Это поддержка или нет?

С другой стороны, сборка проекта текущими средствами вроде как тоже возможна (по крайней мере у меня в живом проекте есть multitarget-сборка, где наряду с NET6 есть NetFx3.5, и это даже работает, правда не через `dotnet build`, а через msbuild)

А при чём здесь только шестой рантайм?

При том что это единственный рантайм, который через три месяца не EOL.

Это поддержка или нет?

Нет. Производитель рантайма чёрным по белому сообщает сроки поддержки версий рантаймов. То что технически вы сегодня можете запустить Windows 95 не означает что он поддерживается. Секьюрити-дыры в нём никто уже править не будет.

Дальше - да ничего - так и буду на нём сидеть ;-) его же никто не удалит, а коли надо - так поставят - а коли не поставят - перейду на .net 6

Если нужны строковые перечисления, как вариант - статический клас с public const string

Перечисления часто сравниваются друг с другом - сравнивать длинные строки, наверное не лучшая затея. А так - "enum class" сравнение наверное, всё-таки должно быть ссылочное - т.е.

enum class En { 

A = "A",

B = "A",

C = "A"

}

En.A != En.B != En.C

//но 

En.A.ToString()==En.B.ToString()

//и

En.A=="А"==En.B

//и

(string)En.A == (string)En.B == (string)En.C

En.ValueEquals(En.A, En.B)

//и так, наверное, тоже можно было бы сделать так

En.A.Value==En.Value.B==En.Value.C

Вот, лучше бы с C# добавили сокращённый точечный синтаксис для перечислений и статических свойств (как в Swift):

enum En (A, B, C);
En func(En en) => en switch
{
.A => .B,
.B => .C,
.C => .A
};

En v = .C;
if (func(.А)==.B)
v = .A;

в том числе для атрибутов

class MyAttribute : Attribute
{
  enum En (A, B, C);

  public MyAttribute() {}
  public MyAttribute(En en) {Enm = en==.A ? .B : .C ;}
  public En Enm {get; private set;} = .B;
}

[My(.A)]
class MyClass {}

ну и по-прежнему ждём первичные конструкторы классов, предварительно заявленные в C#11 - но, видимо, так и не добравшиеся (пока только в record'ах появились; вот бы ещё атрибуты на записях бы завезти). Как и составные типы (объединения) - что ещё в C#10 заявляли - но пока что-то больше ни слуху ни духу (хотя в F# они есть)

using static в том числе можно с enum-ами использовать.

Другое дело что он file-scoped, а хотелось бы чтобы область видимости была в указанных скобках.

вот именно - все enum заранее подключать не комильфо - да и могут их значения с другими идентификаторами пересечься. Да и не только про enum я говорил - а вообще про все статичные мемберы с локальным подхватыванием контекста конкретного ожидаемого типа!

Другое дело что он file-scoped

Так в c# 10 завезли global using и implicit using...

Ещё большая гадость.

Я бы хотел иметь возможность делать using static ... внутри метода.

Мне в C# уже очень давно не хватает оператор функции-помощника with из Kotlin - дающего возможность обращаться к членам объекта-аргумента напрямую (как в членах класса - без указания явного this). В C# это ключевое слово занятно (и по смыслу не подходит текущая реализация), а вот, ключевое слово using - как раз семантически подходило бы, но его давно замкнули в инструкциях на IDisposable интерфейс - что, на мой взгляд, семантически было не совсем верно, но тоже уже не даёт возможности задействовать его для быстрого доступа к членам объекта (Dispose в этом случае не обязателен). Нужно вводить новое ключевое слово - увы - например хорошим вариантом было бы слово within. Но начнутся некоторые неудобности при комбинации двух ключевых слов using и within с одним объектом.

Да в C# и так уже навводили кучу лишних ключевых слов. Вот в Kotlin тот же with (и кучу других "операций") - это не ключевые слова, и даже не макросы - а обычные функции расширения - просто в Kotlin отличная техника подхватывания правого блока кода как последнего аргумента функции - вот она и позволяет писать такие мощные функции, без внесения изменений в синтаксис языка. Вот такой фишки в C# не хватает больше всего (ну разве что больше - не хватает макросов а-ля Nemerle). Вот только в Kotlin подхватывается только блок кода - одна инструкция не подхватывается, увы. И подхватывается только справа, а инфиксная функция не может подхватывать ещё и слева. Впрочем, в C# инфиксных функций вообще нет :-( про Java вообще молчу

with - достаточно удачное заимствование Котлина из Паскаля, согласен.

C# сейчас начинает страдать от проблем всех зрелых языков — это когда свобода скована поддержкой обратной совместимости.

Потому что наплодили ключевых слов - и плодят дальше. Но всё-таки выход из положения обычно всегда найти можно - вот я предложил очередное ключевое слово "within"! Кстати, для совмещения using и within - можно было бы так писать using within (obj) { } - ведь пишут же так "out var argument" или так "ref var variable = ref source", иди даже такая хрень "value is var results " когда надо совместить модификатор и оператор ключевое слово определения переменной.

В общем, выкрутиться можно было бы, чтобы ввести такую конструкцию. Но в целом да - проблема тянется из неудачно введённого using для авто-Dispose()

Sign up to leave a comment.

Articles