Pull to refresh

Comments 74

`someInstance && someInstance.someMethod && someArgs && someInstance.someMethod(someArgs);` <= Работает почти везде.
Ну вы по цепочке свойства повызывайте. Плюс, сравните по читаемости.
`someInstance && someInstance.someProperty && someInstance.someProperty.someField && someInstance.someProperty.someField.someLink && someInstance.someProperty.someField.someLink && someInstance.someProperty.someField.someLink.someMethod && someInstance.someProperty.someField.someLink.someMethod()` <= Пожалуйста. Работает даже там, где нет `?.`

Встречный вопрос: а вы действительно считаете подобный уровень вложенности нормальным?
При пробеге по графу JSON/XML в попытках понять, что же нам такое подсунули — вполне.
А не лучше ли использовать в этом случае рекурсивный парсинг?
Если у меня туева гора типов документов и один от остальных отличается тем, что на 5-ом уровне вложенности висит какой-то элемент с нужным неймспейсом (типичный пример — протокол XMPP), то проще написать однострочник, нежели изобретать сложную инфраструктуру парсинга.
Я не совсем представляю себе документ, о котором вы говорите, но в разных языках есть разные способы доступа к элементам любой вложенности XML. Например, у нас (AS3) можно обратиться к нему по имени напрямую, вроде: rootElem..seventhElem. В языках с утиной типизацией ещё проще — можно «преобразовать» документ к иерархии типов и прямо узнать, привёлся ли он. Но согласиться с тем, что обращение с подобной вложенностью непрерывных вызовов — нормально, я не могу.
Это в каком языке, JS? Даже без явных проверок на != null выглядит абсолютно нечитаемо.
Такой синтаксис поддерживает много кто. Например, ECMA-языки, да. Но в теории должно быть вообще достаточно лишь наличия ленивых вычислений.
С ECMA-языками на самом деле начинается полный трэш и угар — оказывается, что пустая строка равна false, а пустой массив — true, ну и так далее по списку (где-то, кажись, даже строка '0' равнялась false). Остерегайтесь применения булева контекста к неизвестным объектам, друзья. Это может закончиться болью.
Вот поэтому я и люблю шарпик — в нём подобных неочевидностей существенно меньше.
Вполне очевидно, что ссылка на пустой массив приведётся к true, ведь объект, на который ссылается переменная существует в памяти и у программиста есть доступ к полям и методам непосредственно массива. А вот при доступе к элементам, хранимым в массиве, возможны ошибки. В таком случае вам необходима лишь ещё одна проверка:

`someArray && someArray[index] && someArray[index].someMethod && someArray[index].someMethod()`
но:
`someArray && someArray.concat && someArray.concat()`

В JS больнее, согласен, но с динамикой всегда всё плохо. В случае со статической типизацией жизнь сразу становится проще.
В ином контексте, действительно, очевидно, что ссылка на пустой массив приведётся к true. Но в контексте JS и с учётом того, что пустая строка равна false, для меня это очевидным быть перестаёт (пустая строка для меня, уж простите, по-паскалевски ассоциируется с пустым массивом символов, как бы это ни было семантически отдалено в современных языках и рантаймах — хотя вот в Haskell или Erlang, например, это соответствие идеально прослеживается).

В целом я с вами согласен — конечно, всегда можно нагородить море проверок перед вызовом метода, чтобы не было падений при вызове методов и обращении к свойствам. И вот ещё, кстати, C#6 не предлагает полноценного решения описанной вами проблемы — обращения к свойству только если индекс присутствует в массиве. Кстати пришёлся бы синтаксис наподобие

someArray?[index]?.Foo()?.Bar()

Однако в целом начинание, на мой взгляд, неплохое. Мне в реальности довольно часто приходится городить этажерки из трёх-четырёх проверок на null перед вызовом свойств, как бы неприятно это ни было. И отлично, что C#6 мне поможет сократить количество такого кода.
?.. конечно, хорош. Сейчас спасаемся extension методом Eval:

var location = vendor.Eval(x => x.ContactPerson).Eval(x => x.HomeAddress).Eval(x => x.LineOne);


Большинство вещей из C# 6 хочется уже сейчас
а как и когда эти новшества обычно появляются?
p.s. я только изучаю шарп и не сталкивался пока что.
Обычно с релизом новой версии Microsoft Visual Studio, но теперь писать на C# не обязательно в MSVS, можно даже просто в Sublime Text, но не назвал бы такую «среду разработки» удобной. Мне в OS X проще поставить Xamarin Studio, чем пытаться сделать из продвинутого блокнота IDE.

А так теперь есть Roslyn и компилятор C# теперь такая же служба/библиотека, как и остальные компоненты .NET
Проект Mono обещают C# 6 к версии 4.0.0, а для реализации от Microsoft уже доступна MSVS 2015 CTP
А как реализован Eval?
Ну лично у нас используется монадка With
    public static class Monads
    {
        public static TResult With<T, TResult>(this T obj, Func<T, TResult> func) where T : class 
        {
            if (obj == null)
                return default(TResult);
            return func(obj);
        }
    }

нужно только еще одну перегрузку для nullable и дело в шляпе. Перегрузку можно сделать так, чтобы вызывать с такой же сигнатурой, спасибо Скиту за его пример с EvilMethod :)
Эцсамое. Вместо проверок на null и прочего обычно делаем заглушку из серии
public event EventHandler SomeEvent = delegate {};
После чего можно смело дёргать событие в любом месте с соблюдением потокобезопасности. Но да, новый подход вкуснее.
Ваш подход чреват снижением производительности. Один подписчик дергается очень быстро, а два (пустой + настоящий) дергаются заметно медленнее, чем один * 2.
В 95% случаев события используются в UI и прочих местах, где такие мелочи не критичны совершенно. Если же биться за такты процессора, то и LINQ2Objects нельзя использовать, тормозит-с.
А у вас есть конкретные примеры замеров?
На SO были. Примерно 3 наносекунды тратится на вызов пустого делегата.
На СО ещё говорили, что если много таких по коду, лучше в статический завернуть, что бы не создавать много раз одно и то же
UFO just landed and posted this here
немного непривычно читать такой код.

Ну, нововведения всегда сперва непривычны. Поверьте, на такой синтаксис подсаживаешься мгновенно: писать и читать код становится намного проще.
UFO just landed and posted this here
Как я понимаю, в 99% случаев IL будет абсолютно одинаковый, так что разнице в скорости абсолютно никакой не будет (это ведь просто сахар, а не новая возможность платформы). Единственное, нужно посмотреть как происходит работа с событиями из второго примера. Но, в любом случае, если вам каким-то чудом удастся получить проседание по производительности, то никто не мешает написать отдельный кусок логики в старом стиле.
UFO just landed and posted this here
Это синтаксический сахар, так что работать будет точно так же.

Исходный код на C# 6:

var xml = XElement.Parse("<a><b><c attr=\"x\" /></b></a>");
var attr = xml.Element("b")?.Element("c")?.Attribute("attr")?.Value;

Рефлектор показывает следующий код:

XElement xElement = XElement.Parse("<a><b><c attr=\"x\" /></b></a>");
XElement expr_1C = xElement.Element("b");
string arg_54_0;
if (expr_1C == null)
{
	arg_54_0 = null;
}
else
{
	XElement expr_32 = expr_1C.Element("c");
	if (expr_32 == null)
	{
		arg_54_0 = null;
	}
	else
	{
		XAttribute expr_48 = expr_32.Attribute("attr");
		arg_54_0 = ((expr_48 != null) ? expr_48.Value : null);
	}
}

Быстрее с точки зрения выполнения тут никак не сделаешь. Но кода, согласитесь, стало в разы меньше.
UFO just landed and posted this here
А что если в примере с IDisposable использовать следующий подход?

public void RetireHenchman()
{
    using(Minion)
        Minion = null;
}
В оригинальном примере нам не известно, реализует ли Minion интерфейс IDisposable, а значит нельзя просто так взять и запихать его в using.
Можно запихать так:

using(obj as IDisposable)
obj = null;

Ну и такого никто не отменял:

public static void DisposeSafe(this object obj)
{
var disp = obj as IDisposable;
if(disp == null)
{
return;
}
disp.Dispose();
}
Нельзя
object obj = new object();
using (var disposable = obj as IDisposable)
   disposable = null;

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

Конечно, компилятор можно обмануть
object obj = new object();
IDisposable disposable;
using (disposable = obj as IDisposable)
   disposable = null;
Console.WriteLine(obj == null);

но это из разряда назло маме отморожу уши
Вы изменили мой код и он перестал работать для вас, мой код в чистом виде, а не переписанный вами очень даже компилируется и работает. Я не говорю, что это хорошая практика, я просто показываю, что using можно использовать для этого кейса.
Да, посмотрел получше — действительно код рабочий. Но извращенский все равно.
Да способ не самый удачный, я использую приведенный выше DisposeSafe
Лично я просто использую using. Проще написать внутри юзинга try catch, чем расписывать весь юзинг вручную.
a?.b

полный аналог

a == null ? null : a.b

А дальше? Если цепочка будет длинее, проперти вычисляются только 1 раз? Тоесть это несколько вложенных if с локальными переменнами для каждого резульата "?."?
Да, вычисление будет только один раз. Противоположное поведение бы разрушило семантику оператора точки, и это было бы вообще печально. Выше товарищ привёл разбор декомпилированного кода, ознакомьтесь.
Кстати, если так, то, насколько понимаю, конструкция

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name")); 

разворачивается в

PropertyChanged == null ? null : PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); 

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

Ведь a?.b должно быть аналогично
var tmp = a;
tmp == null ? null : tmp .b

чтобы код остался потокобезопасным относительно переменной a…

P.S. Вспомнился забавный кусочек в одном из проектов (скорее всего, описка)
var handler = PropertyChanged;
if (handler != null) PropertyChanged(o, e);
Тоже так подумал сперва. Но, судя по этому комментарию, всё нормально — на каждый "?." вводится локальная переменная, которая используется для сравнения и последующего обращения.

Похоже на ситуацию с "++i и i++" (как работает). Короткое простое объяснение недостаточно в некоторых случаях.
>Раньше мне пришлось бы писать код с большим уровнем вложенности, проверяя на null каждое значение в цепочке:

Ну эта проблема давно была решена исспользованием MayBe. На практике распространения особого не получил, потому что в real life «дальнейшее вычисление выражения производится не будет» означает «кидаем исключение „вендор без контактного лица, персона без адреса, business logic failed всё пропало!“.
Что на практике ну вообще неотличимо от „NRE: ContactPerson.HomeAddress doesn't have 'FirstLine' blah-blah-blah...“

>я хочу создать запрос с использованием метода SingleOrDefault(), а у полученного объекта (если он существует) обратиться к некоторому свойству.

Cнова какой-то оторваный от жизни пример. Мы же обращаемся к свойству не просто так, а с какой-то целью.

Было:

var e = entity.SingleOrDefault();
if (e != null)
return e.Field1;
else
{

}

Cтало

var cntnt = entity.SingleOrDefault()?.content;
if(e != null)
return cntnt
else
{

}

>Случай второй: я хочу вернуть значение null, если исходная последовательность равна null

Зачем это может понадобиться? Акакдемизм в чистом виде.
>Впрочем, race condition никто не отменял…
Ну вот, и вы туда же. Из одного race condition сделали другой, но еще хуже. Потому что в первом случае ошибка возникнет в строке
PropertyChanged(this, new PropertyChangedEventArgs(«Name»));
и всё будет сразу понятно.
А в «исправленном» случае, подписчику может прийти событие после того, как он отписался, и это будет для него неожиданностью и приведет к неизвестным последствиям. Заманаетесь искать причину. Уже сталкивался с подобным, так что знаю, что говорю.
Ну, во-первых, это не я туда же, это пост Била Вагнера. С вашим комментарием согласен, но есть два вопроса:
1. А как надо делать? Я так и не придумал более безопасного способа события вызывать.
2. А у вас есть рабочий минимальный пример, в котором подобная проблема возникает? Я в своё время пытался воспроизвести описанную проблему, но у меня так ничего и не получилось.
Сейчас код не восстановлю, но повторял так (после прочтения вот этого поста):
было событие, и два подписчика на него.
При вызове события у первого стоял Thread.Sleep, во время которого второй отписывался от события и засовывался в GC.
А как вы засунули объект в GC если на него есть живая ссылка?
Что-то я напутал с GC, но вот kosmos89 передал идею точнее.
Есть два подписчика: A и B.
При возникновении события A отписывается и заставляет B отписаться тоже. В первом случае получим NullReferenceException при вызове события и нам сразу будет ясно, что все отписались. А в случае копирования делегата придется долго выяснять, почему же в Б что-то сломалось, ведь отписка-то произошла!
На само деле проблема решается только заменой мультикаст делегата на своё решение, которое позволяет отписываться в момент его вызова.
Ну как-то грустно вместо родной системы событий .NET городить рядом ещё одну. Хотелось бы найти более нормальный способ решить проблему.
Этот святой грааль ищут давно и очень даже безуспешно :)
Этот пример не валиден тк MulticastDelegate не гарантирует порядок исполнения. Даже в «хорошем» случае (когда по идее все должно падать на NPR) сначала может быть вызван B а только потом A.
А почему нельзя и в B сделать отписку от события для A?
Можно — но никто не гарантирует какой event handler будет вызван первым.
Если вам это не важно, то не понятно зачем вам 2 event handler. Все можно сделать и одним. И код получится намного чище.
Mutual exclusive event handlers — такая штука не поддерживается Multicast Delegate и я не могу найти не одного примера где это действительно полезно.
Пример валиден. Порядок не важен. Главное, что может так произойти, что вызовется в «плохом» порядке, что у меня на практике и случилось.
Если код зависит от порядка вызова делегатов при возникновении события, это однозначно очень плохой код!
Код хороший. Код сам по себе не зависит от порядка вызова. Еще раз повторяю для невнимательных: виноват MulticastDelegate, который вызывает подписчика ПОСЛЕ того, как он отписался. Подписчик просто не готов к такой подлости.
MulticastDelegate никогда не вызывает подписчика после того как он отписан.
Каждый раз как вы подписываетесь на событие (event) или отписываетесь от него создается новый MulticastDelegate. По этому и создается впечатление что он вызывает подписчика после отписки, но это не так. Вообще MulticastDelegate по использованию — imutable. По этому в приведенном примере (где MulticastDelegate перед вызовом копируется в локальную переменную) все работает корректно, но не так как вы ожидали.
На счет того что код хороший — не могли бы вы привести пример где такой подход (когда подписчики события должны знать что-либо друг о друге) является хорошим кодом. У меня в голову ни один пример не приходит.
Случай когда подписчик отписывает себя сам — предлагать не надо, он бывает полезный, но к рассматриваемой проблеме никак не относится.
А использует и владеет Б. Оба подписаны на одно и то же глобальное событие. В некоторых случаях по событию А уничтожает Б, Б при этом отписывается от события, но все равно его получает, когда уже находится в невалидном состоянии.
Во втором, генерация события должна быть отдельным методом, тогда никто ничего не забудет.
Ваш пример это костыль для подпорки костыля.
Почему никто не обращает на значок «Перевод»? Все примеры предоставлены Билом Вагнером. От себя замечу, что носят они демонстративный характер: чтобы в несколько строк показать общую идею.
var location = vendor?.ContactPerson?.HomeAddress?.LineOne;

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

var location = vendor?.ContactPerson.HomeAddress.LineOne;
Нет. Если ContactPerson равно null, то выражение vendor?.ContactPerson.HomeAddress упадёт.
Насколько я помню, вариант с единственным вопросом в начале всего выражения рассматривался при разработке C#6, но в итоге так и не был использован.
Радует, что C# 6 проектировали адекватные люди =) Мне было бы очень грустно, если бы выбрали вариант с одним вопросиком.
А, понял. Person не должен быть null, а Address null тогда крах.
Возможно, многие знают, но что касается событий, то давно существует изящный способ избавления от проверки на null:

public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { };

Просто, лаконично, потокобезопасно и удобно.
Sign up to leave a comment.