Здравствуйте, Alekzander, Вы писали:
_FR>>А когда-нибудь можно будет просто `foreach(var item in collection ?? []) {` без проверки на `null` A>Раз речь зашла про потенциальные изменения в языке.
A>Интересно, а что-нибудь ухудшилось бы, если бы мы всегда трактовали null как пустую коллекцию? (Можно было бы автоматически генерировать проверку на null).
Это не всегда возможно, особенно с изменяемыми коллекциями. Представьие, что ваша коллекция с приватным конструктором, которая при вызове GetEnumerator() пишет что-то в диагностику. "трактовать null как пустую коллекцию" в этом случае (как и во многих других, более реальных) не получится. Поэтому гайдлайнами и рекомендуется явно самим этим заниматься:
❌ DO NOT return null values from collection properties or from methods returning collections. Return an empty collection or an empty array instead.
The general rule is that null and empty (0 item) collections or arrays should be treated the same.
В восьмом дотнете тут слегка упростили жизнь, добавив статическое свойство Empty в стандартные ридонли коллекции.
A>Кому-то сильно нужен прям NRE вместо отсутствия действий, если коллекции нет?
Никому не нужен, но раз уж 1) пользователям позволено заводить свои коллекции и 2) иметь различное поведение для пользовательских и "известных компилятору" коллекциях было бы нежелательно (вызовет больше неразберихи и ошибок), то настоятельно рекомендуется решать это самостоятельно.
Я работал с библиотеками, где разработчики не сильно об этом задумывались и как итог где-то объект мог возвращать null в свойстве, где-то пустую коллекцию. В каких-то сценариях необходимо было перезаписывать свойство, а где-то строго менять существующий экземпляр. Это сложно, а что-то было по-нормальному (небыло бы неожиданных ошибок) приходилось обкладывать это всё своими обёртками. Такое себе удовольствие. То, что предлагает сам .NET намного лучше и удобнее, если следовать рекомекндациям.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, karbofos42, Вы писали:
K>Вопрос только зачем разрешили инициализаторы для уже существующих объектов. K>Замени A = { 2 } на A = new List<int> { 2 } и не будет проблемы.
Это как раз очень важно. Обычно свойста "коллекционного" типа делают ридонли, потому что иначе может понадобится решать много вопросов дизайна: запрещать или разрешать присваивать null и как это потом обрабатывать и как оповещать об изменениях, когда нужно: а это сразу и сценарии биндинга и какого-то change tracking-а и много чего ещё. В общем неудобно и не всегда понятно как работать с такими свойствами. Поэтому основной как раз сценарий — это когда свойство-коллекция readonly.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Разраб, Вы писали:
Р>С удивлением обнаружил вот такое поведение инициализации списка. Какие еще похожие приколы есть в шарпах? а говорили шарп простой
Collection initializers let you specify one or more element initializers when you initialize a collection type that implements IEnumerable and has Add with the appropriate signature as an instance method or an extension method.
Здравствуйте, Разраб, Вы писали:
Р>Здравствуйте, gandjustas, Вы писали:
G>>Здравствуйте, Разраб, Вы писали:
Р>>>С удивлением обнаружил вот такое поведение инициализации списка. Какие еще похожие приколы есть в шарпах? а говорили шарп простой G>>Примерно с C# 2.0, скоро 20 лет. С подключением.
Р>не, там проблема что к списку добавляется двойка. я об этом.
Да, потому что такой синтаксис вызывает метод Add, появилось в c# 2.0
Здравствуйте, Разраб, Вы писали:
Р>С удивлением обнаружил вот такое поведение инициализации списка. Какие еще похожие приколы есть в шарпах? а говорили шарп простой
Р>Foo a = new Foo
Р>{
Р> A = { 2 }
Р>};
Р>foreach (var x in a.A)
Р> Console.WriteLine($"{x}");
Р>class Foo
Р>{
Р> public List<int> A { get; set; } = new List<int> { 1 };
Р>}
Р>и главное ПОЧЕМУ так? что это за фича?
Object and Collection Initializers появились в С# 3.0 (как говорит история, это конец 2007). Почитайте, там много интересных особенностей, например про сигнатуры методов Add и индексеры. И свойство коллекции вовсе не нужно делать `set`-абельным.
Мне кажется невероятно удобной и полезной, не хватает лишь небольшого расширения:
public static void Add<T>(this ICollection<T> source, IEnumerable<T>? collection) {
ArgumentNullException.ThrowIfNull(source, nameof(source));
if(collection is not null) {
foreach(var item in collection) { // А когда-нибудь можно будет просто `foreach(var item in collection ?? []) {` без проверки на `null`
source.Add(item);
}//for
}//if
}
// И до кучи такие оптимизацииpublic static void Add<T>(this List<T> source, IEnumerable<T>? collection) {
ArgumentNullException.ThrowIfNull(source, nameof(source));
if(collection is not null) {
source.AddRange(collection);
}//if
}
Тогда становится возможным использовать в инициализации не только отдельные объекты, но и коллекции объектов:
var instance = new Item {
Children = {
"A",
"Before required",
Configuration.RequiredItems, // Какая-то коллекция"Before optional",
GetOptionalItems(), // Другая коллекция
},
};
Без иметода-расширения синтаксис инициализотора использоваться бы не получилось.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Alekzander, Вы писали:
A>Интересно, а что-нибудь ухудшилось бы, если бы мы всегда трактовали null как пустую коллекцию? (Можно было бы автоматически генерировать проверку на null). Кому-то сильно нужен прям NRE вместо отсутствия действий, если коллекции нет?
В каждый foreach добавилась бы дополнительная проверка.
Вроде мелочь, а вроде и лучше в языке иметь удобную обёртку без всего лишнего, а кому надо расширит для себя.
Так же периодически бывает, что меняешь foreach на for/while, а тут уже легко забыть про null и получить ошибку в рантайме.
Здравствуйте, Разраб, Вы писали:
Р>С удивлением обнаружил вот такое поведение инициализации списка. Какие еще похожие приколы есть в шарпах? а говорили шарп простой
Примерно с C# 2.0, скоро 20 лет. С подключением.
Здравствуйте, karbofos42, Вы писали:
_FR>>Это как раз очень важно. Обычно свойста "коллекционного" типа делают ридонли, потому что иначе может понадобится решать много вопросов дизайна: запрещать или разрешать присваивать null и как это потом обрабатывать и как оповещать об изменениях, когда нужно: а это сразу и сценарии биндинга и какого-то change tracking-а и много чего ещё. В общем неудобно и не всегда понятно как работать с такими свойствами. Поэтому основной как раз сценарий — это когда свойство-коллекция readonly.
K>Заполнять коллекцию в инициализаторе нет никакой необходимости. K>Инициализатор — это в первую очередь про значения свойств, а не содержимое объектов.
Ещё какая есть "необходимость". Мне кажется очень удобным при инициализации в одном выражении задать сразу всё, что нужно для экземпляра. Мне это кажется гораздо более простым и наглядным, чем вызов методов, которые меняют переданные в них коллекции. Далее, раз уж это одно выражение, можно использовать его в expression bodied members, что в целом заметно упроощает на мой взгляд код.
K>Заполнять коллекции в инициализаторе вообще как-то мне не нравится.
Сложно на эо как-то возразить, не видя что именно не нравится. Ну нет так нет.
K>В плане производительности бонусов нет (просто дёргается метод Add), запись не так, чтобы красивая. K>Не инициализатор получается, а какая-то простыня с заполнением разных коллекций.
Как раз инициализатор. Ведь что простыня с "заполнением разных коллекций", что "простыня" с заполнением рядовых свойств — выглядит всё одно. Топикстартер не обратил на это внимание и другие видимо так же это упускают, что и обычные инициализаторы объектов работают по тому же принципу:
using System;
var o = new MyObject {
Value = {
C = "Initializer",
},
};
Console.WriteLine(o); // A = "My Value Constructor", B = "MyObject Constructor", C = "Initializer"class MyObject
{
public MyValue Value { get; } = new MyValue { B = "MyObject Constructor", };
public override string ToString() => Value.ToString();
}
class MyValue
{
public string A { get; set; } = "My Value Constructor";
public string? B { get; set; }
public string? C { get; set; }
public override string ToString() => $"A = \"{A}\", B = \"{B}\", C = \"{C}\"";
}
Вот что делает инициализатор из этого вот (очень часто встречающегося в моей по крайней мере практике кода):
// Былоvar o = new MyObject();
o.C = x1;
o.D = new();
o.D.Z = "aaa";
o.V.Add(1);
// Можно сделать:var o = new MyObject {
С = { x1, },
D = new() { Z = "aaa", },
V = { 1, },
};
Никаких повторений ("o." и т.п.) и поэтому намного нагляднее. И многим людям хочется улучшить этот синтаксис, добавить возможность подписки на события, заводить переменные и т.п. Но ваше право не пользоваться этим.
K>По-моему нужно было добавить удобное заполнение и передачу коллекций, а не в инициализаторы подобные фичи добавлять. K>В общем, по-моему лучше бы добавили удобное добавление групп значений в коллекции, а не ограничились костылями в инициализаторах. K>В идеале ещё, чтобы в тот же List добавлялись группы значений с оптимизацией, которая есть в AddRange.
Ну так что мешает обдумать эту идую и предложить разработчикам языка или стандартной библиотеки? Может быть и действительно ваша идея окажется лучше.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, karbofos42, Вы писали:
_FR>>Это не всегда возможно, особенно с изменяемыми коллекциями. Представьие, что ваша коллекция с приватным конструктором, которая при вызове GetEnumerator() пишет что-то в диагностику. K>Не вижу проблемы.
Вы напрасно удалили вопрос, на который я отвечал. Он звучал так: "…если бы мы всегда трактовали null как пустую коллекцию". То, что вы написали ниже, это совсем не "всегда трактовали null как пустую коллекцию", это всего-лишь "фикс" вокруг foreach. Сценарии же использования могут быть очень разными, от вызова ToString() до рукописного вызова GetEnumerator() или вызова какого-либо метода из Enumerable.
K>Можно же было сделать подобное:
Именно этого я и имел в виду говоря о "// А когда-нибудь можно будет просто `foreach(var item in collection ?? []) {` без проверки на `null`".
В одной из будущих версиях компилятора могут сделать оптимизацию, при которой вот такой вот паттерн
foreach(var item in collection ?? []) {
// …
}//for
будет превращаться в (условно, здесь: "emit foreach (var x in y ?? []) as a lifted null check that executes nothing if 'y' is null.")
if(collection is not null) {
foreach(var item in collection) {
// …
}//for
}//if
Но опять же тут пользователь явно декларирует, что 1) ожидает нулевую ссылку и 2) хочет пропустить итерацию в этом случае. Это снова не "всегда трактовали null как пустую коллекцию", потому что этого и не стоит добиваться.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, Разраб, Вы писали:
Р>>С удивлением обнаружил вот такое поведение инициализации списка. Какие еще похожие приколы есть в шарпах? а говорили шарп простой G>Примерно с C# 2.0, скоро 20 лет. С подключением.
не, там проблема что к списку добавляется двойка. я об этом.
Здравствуйте, karbofos42, Вы писали:
K>Вопрос только зачем разрешили инициализаторы для уже существующих объектов. K>Замени A = { 2 } на A = new List<int> { 2 } и не будет проблемы.
к счастью, я так не инициализирую коллекции, либо new, дибо Add
Здравствуйте, Разраб, Вы писали:
Р>>>С удивлением обнаружил вот такое поведение инициализации списка. Какие еще похожие приколы есть в шарпах? а говорили шарп простой G>>Примерно с C# 2.0, скоро 20 лет. С подключением. Р>не, там проблема что к списку добавляется двойка. я об этом.
Потому что это именно самое ожидаемое. В вашем случае тип Foo кажется удобнее объявить вот так вот:
class Foo
{
public Foo() : this(1) { }
public Foo(params int items) => A.AddRange(items);
public List<int> A { get; } = new();
}
Раз уж есть сценарий, когда снаружи необходимо задать содержимое, то можно вынести это содержимое в конструктор. Тогда пользователь может или перезаписать содержимое (используя конструктор) или дополнить, используя инициализатор.
Почему свойство-коллекцию удобнее делать readonly я как-то писал в Коллекция как тип свойства.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>А когда-нибудь можно будет просто `foreach(var item in collection ?? []) {` без проверки на `null`
Раз речь зашла про потенциальные изменения в языке.
Интересно, а что-нибудь ухудшилось бы, если бы мы всегда трактовали null как пустую коллекцию? (Можно было бы автоматически генерировать проверку на null). Кому-то сильно нужен прям NRE вместо отсутствия действий, если коллекции нет?
I'm a sewer mutant, and my favorite authors are Edgar Allan Poo, H.G. Smells and George R.R. Martin.
Здравствуйте, _FRED_, Вы писали:
_FR>Это как раз очень важно. Обычно свойста "коллекционного" типа делают ридонли, потому что иначе может понадобится решать много вопросов дизайна: запрещать или разрешать присваивать null и как это потом обрабатывать и как оповещать об изменениях, когда нужно: а это сразу и сценарии биндинга и какого-то change tracking-а и много чего ещё. В общем неудобно и не всегда понятно как работать с такими свойствами. Поэтому основной как раз сценарий — это когда свойство-коллекция readonly.
Заполнять коллекцию в инициализаторе нет никакой необходимости.
Инициализатор — это в первую очередь про значения свойств, а не содержимое объектов.
Заполнять коллекции в инициализаторе вообще как-то мне не нравится.
В плане производительности бонусов нет (просто дёргается метод Add), запись не так, чтобы красивая.
Не инициализатор получается, а какая-то простыня с заполнением разных коллекций.
По-моему нужно было добавить удобное заполнение и передачу коллекций, а не в инициализаторы подобные фичи добавлять.
Я вот иногда такие обёртки пишу:
public void Process(IEnumerable<int> items)
{
foreach (var item in items)
{
...
}
}
public void Process(params int[] items)
{
Process((IEnumerable<int>)items);
}
потому что так получится и вызвать метод с любой коллекцией и так же с некими фиксированными значениями:
Process(1,2,3,4);
Можно конечно не добавлять вариант с params и писать вызов как:
Process(new int[]{1,2,3,4});
но неудобно такое писать каждый раз и выглядит некрасиво.
Ну, и просто бывает нужно для уже существующей коллекции что-то добавлять и там уже не вызовешь инициализатор, а будешь дёргать метод Add/AddRange.
В общем, по-моему лучше бы добавили удобное добавление групп значений в коллекции, а не ограничились костылями в инициализаторах.
В идеале ещё, чтобы в тот же List добавлялись группы значений с оптимизацией, которая есть в AddRange.
Здравствуйте, _FRED_, Вы писали:
_FR>Это не всегда возможно, особенно с изменяемыми коллекциями. Представьие, что ваша коллекция с приватным конструктором, которая при вызове GetEnumerator() пишет что-то в диагностику.
Не вижу проблемы.
Сейчас такой код:
var items = new List<int> {1,2,3};
foreach (var item in items)
{
...
}
превращается во что-то вида:
var items = new List<int> {1,2,3};
var enumerator = items.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
...
}
}
finally
{
enumerator.Dispose();
}
Можно же было сделать подобное:
var items = new List<int> {1,2,3};
var enumerator = items?.GetEnumerator();
if (enumerator != null)
{
try
{
while (enumerator.MoveNext())
{
...
}
}
finally
{
enumerator.Dispose();
}
}
ну, или только items на null проверить и надеяться, что GetEnumerator нормально написан и null не возвращает.
Здравствуйте, _FRED_, Вы писали:
_FR>Ещё какая есть "необходимость". Мне кажется очень удобным при инициализации в одном выражении задать сразу всё, что нужно для экземпляра. Мне это кажется гораздо более простым и наглядным, чем вызов методов, которые меняют переданные в них коллекции. Далее, раз уж это одно выражение, можно использовать его в expression bodied members, что в целом заметно упроощает на мой взгляд код.
Это удобно для небольших дополнений к конструктору.
Если нужно настроить десяток свойств, что-то в коллекции добавить, то выходит какая-то огромная простыня на пару экранов с дикой вложенностью с кучей скобочек.
_FR>Как раз инициализатор. Ведь что простыня с "заполнением разных коллекций", что "простыня" с заполнением рядовых свойств — выглядит всё одно. Топикстартер не обратил на это внимание и другие видимо так же это упускают, что и обычные инициализаторы объектов
По-моему основная плюшка инициализаторов — это set свойств { get; init; }
Удобно builder'ы на каждый чих не городить и конструкторы с десятком параметров не нужно писать.
В остальном сомнительный синтаксический сахар.
Ну, пару свойств для того же сериализатора Json прописать удобно и я использую.
Чуть сложнее сценарий — как-то предпочитаю инициализаторы стороной обходить.
_FR>Вот что делает инициализатор из этого вот (очень часто встречающегося в моей по крайней мере практике кода):
Только на практике названия свойств будут несколько длиннее и их количество несколько больше.
Отдельно "нравится" как внезапно начинают фигурные скобочки проставляться как в Java.
_FR>Никаких повторений ("o." и т.п.) и поэтому намного нагляднее. И многим людям хочется улучшить этот синтаксис, добавить возможность подписки на события, заводить переменные и т.п. Но ваше право не пользоваться этим.
Можно было взять из Delphi with (WinForms же по опыту VCL сделали, явно и к конструкциям языка присматривались) и получить что-то вроде:
// сейчас без with
o.D.A = "a";
o.D.B = "b";
o.D.C = "c";
// могло бы быть с with
with (o.D)
{
A = "a";
B = "b";
C = "c";
}
Но посчитали, что это плохая конструкция, которая больше вредит, а инициализаторы лучше.
Правда для инициализаторов по традиции немного синтаксис кривой сделали и вместо ; разделителем выступает ,
_FR>Ну так что мешает обдумать эту идую и предложить разработчикам языка или стандартной библиотеки? Может быть и действительно ваша идея окажется лучше.
Если за столько лет не сделали, значит не придумали или не захотели.
Я не настолько крут, чтобы что-то такое реализовывать и продавливать.