Глубокая пустая проверка, там лучший путь?

Примечание: Этот вопрос задали перед введением .? оператор в C# 6 / Visual Studio 2015.

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

if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...

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

Это - возможное использование некоторого дополнительного метода, или это была бы функция языка, или это - просто плохая идея?

128
задан Homde 27 August 2017 в 16:08
поделиться

6 ответов

Мы рассмотрели добавление новой операции «?». на язык, который имеет семантику, которую вы хотите. (И это было добавлено сейчас; см. Ниже.) То есть вы говорите

cake?.frosting?.berries?.loader

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

Это не сделало бар для C # 4. Возможно для гипотетической будущей версии языка.

Обновление (2014): ?. Оператор теперь запланирован для следующего выпуска компилятора Roslyn. Обратите внимание, что все еще есть некоторые дискуссии по поводу точного синтаксического и семантического анализа оператора.

Обновление (июль 2015 г.): Visual Studio 2015 был выпущен и отправляется с компилятором C #, который поддерживает NULL-COUNDER COUNDIAL ?. и ? [] .

220
ответ дан 24 November 2019 в 00:35
поделиться

Помимо нарушения закона Демитера, как уже отметил мерхтер Афшари, мне кажется, вам нужен «проверка глубокой нулевой» для логики решений.

Это чаще всего тот случай, когда вы хотите заменить пустые объекты с значениями по умолчанию. В этом случае вы должны рассмотреть вопрос о реализации шаблона объекта NULL . Он действует как средство для реального объекта, обеспечивая значения по умолчанию и «недействительные» методы.

16
ответ дан 24 November 2019 в 00:35
поделиться

Обновление: Начиная с Visual Studio 2015, компилятор C # (языковая версия 6) теперь распознает ?. Оператор, который делает «Проверка глубокого нора» ветерок. См. Этот ответ для деталей.

Помимо повторного проектирования вашего кода, как Этот удаленный ответ предложил, Другой (хотя и ужасный) опция будет использоваться . Попробуйте ... Cav Блок, чтобы увидеть, если NullReenfectionException происходит в течение этого поиска глубокого свойства.

try
{
    var x = cake.frosting.berries.loader;
    ...
}
catch (NullReferenceException ex)
{
    // either one of cake, frosting, or berries was null
    ...
}

Я лично не сделаю это по следующим причинам:

  • Это не выглядит красиво.
  • Он использует обработку исключений, которая должна нацелить исключительные ситуации, а не то, что вы ожидаете, чтобы часто произошло во время обычного курса работы.
  • NullReferenceException S, вероятно, никогда не должно быть поймано явно. (См. Этот вопрос .)

Так возможно, использование некоторого метода расширения или это будет языковая функция, [...]

Это почти наверняка должна быть языковой функцией ( который доступен в C # 6 в виде . . и ? [] операторы), если C # уже не имел более сложную ленивую оценку, или если вы не хотите использовать отражение (который Вероятно, также не хорошая идея по причинам производительности и безопасности типа).

Поскольку нет возможности просто пройти Cake.frosting.berries.loader к функции (это будет оценено и бросить нулевой контрольный исключений), вам придется реализовать метод общего поиска следующим образом: он принимает объекты и имена свойств для поиска:

static object LookupProperty( object startingPoint, params string[] lookupChain )
{
    // 1. if 'startingPoint' is null, return null, or throw an exception.
    // 2. recursively look up one property/field after the other from 'lookupChain',
    //    using reflection.
    // 3. if one lookup is not possible, return null, or throw an exception.
    // 3. return the last property/field's value.
}

...

var x = LookupProperty( cake, "frosting", "berries", "loader" );

(Примечание. Код отредактирован.)

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

[...], Или это просто плохая идея?

Я бы либо останулся с:

if (cake != null && cake.frosting != null && ...) ...

или пойти с вышеупомянутым ответом Mehrdad Afshari.


P.S.: Назад, когда я написал этот ответ, я, очевидно, не рассматривал деревья выражений для функций лямбда; Смотрите е.Г. Ответ @driis для решения в этом направлении. Он также основан на виду отражения и, таким образом, может не выполнить совсем так же хорошо, как более простое решение (, если (...! = NULL & ...! = NULL) ... ), но это может быть оценено приятнее от Синтаксис точка зрения.

10
ответ дан 24 November 2019 в 00:35
поделиться

Считаю ли я это плохим стилем кодирования? Ну да.

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

Есть несколько исключений, где я думаю, что короткие имена переменных в порядке:

Индексы (в основном для циклов), такие как:

for (int i = 0; i < 10; i++)
{
}

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

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

-121--2422244-

Хорошо, я не понимаю, как TempFrame (ваш передний план) может быть серым цветом, если вы используете вычитание фона. Вы, должно быть, используете особый алгоритм. Но предполагая, что TempFrame является серым цветом, вы бы сделали следующее:

cv::Mat mask = tempFrame > 0.5;

cv::Mat outFrame;
capturedFrame.copyTo(outFrame, mask);

Это код OpenCV 2.0 выше. Число 0.5 является порогом, вам нужно установить его на что-то подходящее. Если вы не используете изображения с плавающей запятой, вы, вероятно, установите его на 128 или что-то подобное. Это то же самое в коде OpenCV 1.1:

CvMat* mask = cvCreateMat(tempFrame.rows, tempFrame.cols, CV_8UC1);
cvCmpS(tempFrame, 0.5, mask);

CvMat* outFrame = cvCreateMat(capturedFrame.rows, capturedFrames.cols, CV_32FC3);
cvCopy(capturedFrame, outFrame, mask);
-121--3445791-

Я нашел это расширение весьма полезным для сценариев глубокого вложения.

public static R Coal<T, R>(this T obj, Func<T, R> f)
    where T : class
{
    return obj != null ? f(obj) : default(R);
}

Это идея, которую я получил из нулевого оператора объединения в C # и T-SQL. Хорошо то, что возвращаемый тип всегда является возвращаемым типом внутреннего свойства.

Таким образом вы можете сделать это:

var berries = cake.Coal(x => x.frosting).Coal(x => x.berries);

... или небольшой вариант выше:

var berries = cake.Coal(x => x.frosting, x => x.berries);

Это не лучший синтаксис, который я знаю, но он работает.

24
ответ дан 24 November 2019 в 00:35
поделиться

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

Итак, я создал метод расширения, который позволит вам написать:

var berries = cake.IfNotNull(c => c.Frosting.Berries);

Это вернет ягоды, если ни одна часть выражения не является нулевой. Если будет встречен NULL, нуль возвращается. Хотя есть несколько предостережений, хотя в текущей версии она будет работать только с простым доступом участника, и он работает только на .NET Framework 4, потому что он использует метод Memberexpression.update, который новый в V4. Это код для метода расширения INTNOTNULL:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace dr.IfNotNullOperator.PoC
{
    public static class ObjectExtensions
    {
        public static TResult IfNotNull<TArg,TResult>(this TArg arg, Expression<Func<TArg,TResult>> expression)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            if (ReferenceEquals(arg, null))
                return default(TResult);

            var stack = new Stack<MemberExpression>();
            var expr = expression.Body as MemberExpression;
            while(expr != null)
            {
                stack.Push(expr);
                expr = expr.Expression as MemberExpression;
            } 

            if (stack.Count == 0 || !(stack.Peek().Expression is ParameterExpression))
                throw new ApplicationException(String.Format("The expression '{0}' contains unsupported constructs.",
                                                             expression));

            object a = arg;
            while(stack.Count > 0)
            {
                expr = stack.Pop();
                var p = expr.Expression as ParameterExpression;
                if (p == null)
                {
                    p = Expression.Parameter(a.GetType(), "x");
                    expr = expr.Update(p);
                }
                var lambda = Expression.Lambda(expr, p);
                Delegate t = lambda.Compile();                
                a = t.DynamicInvoke(a);
                if (ReferenceEquals(a, null))
                    return default(TResult);
            }

            return (TResult)a;            
        }
    }
}

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

Я уверен, что это может быть расширено так, чтобы другие выражения, чем Memberexpression поддерживаются. Рассмотрим это как доказательство концептуального кода, и, пожалуйста, имейте в виду, что наказание производительности, используя его (который, вероятно, не имеет значения во многих случаях, но не используйте его в тесной петле :-))

27
ответ дан 24 November 2019 в 00:35
поделиться

Один из вариантов - использовать Null Object Patten, поэтому вместо null, когда у вас нет торта, у вас есть NullCake, который возвращает NullFosting и т. Д. Извините, я не очень хорошо объясняю это, но другие люди см.

4
ответ дан 24 November 2019 в 00:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: