Вопрос стиля / удобочитаемости в отношении оператора C # using

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

Я просматриваю решение, добавляющее операторы using во многих местах. Часто мне попадается что-то вроде этого:

{
    log = new log();
    log.SomeProperty = something;  // several of these
    log.Connection = new OracleConnection("...");
    log.InsertData(); // this is where log.Connection will be used
    ... // do other stuff with log, but connection won't be used again
}

где log.Connection - это OracleConnection, который реализует IDisposable.

Чистильщик во мне хочет поменять его на:

{
    using (OracleConnection connection = new OracleConnection("..."))
    {
        log = new log();
        log.SomeProperty = something;
        log.Connection = conn;
        log.InsertData();
        ...
    }
}

Но любителям краткости и краткости работы хочется сделать:

{
    log = new log();
    log.SomeProperty = something; 
    using (log.Connection = new OracleConnection("..."))
        log.InsertData();
    ...
}

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

РЕДАКТИРОВАТЬ: обратите внимание, что это всего лишь один (несколько надуманный) пример из многих. Пожалуйста, не зацикливайтесь на том, что это происходит для указания класса регистратора с плохо продуманным интерфейсом. Это не имеет отношения к моему вопросу, и я в любом случае не вправе улучшать сами классы.


person Igby Largeman    schedule 20.05.2010    source источник


Ответы (5)


Я согласен с тем, что в идеале log сам должен реализовывать IDisposable, но давайте предположим, что это невозможно, и обратимся к вопросу, который фактически задал OP.

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

Также обратите внимание, что вы можете выполнить другую инициализацию вне блока using. Здесь это не имеет значения, но может иметь значение, если вы «используете» какой-то действительно дорогой ресурс. Это:

log = new log();
log.SomeProperty = something; // This can be outside the "using"
using (OracleConnection connection = new OracleConnection("..."))
{
    log.Connection = conn;
    log.InsertData();
    ...
}
person EMP    schedule 20.05.2010
comment
Спасибо за ответ на актуальный вопрос. Ваше мнение о выполнении инициализации за пределами блока using интересно, потому что единственная причина, по которой я переместил его внутрь блока, заключалась в том, что, по моему мнению, было бы лучше хранить весь связанный код вместе. Я не думал о возможных расходах (здесь не проблема, но важно иметь в виду). - person Igby Largeman; 21.05.2010

Они оба ужасны. Не делайте ни того, ни другого.

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

Вы можете сказать это по своему комментарию: здесь используется соединение, я знаю, что соединение не будет использоваться снова. Откуда ты это знаешь? Вы знаете только это, если это документированный контракт класса. Это не лучший контракт, чтобы навязывать его потребителю класса.

Некоторые способы сделать это лучше:

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

2) заставить InsertData принимать соединение как параметр. Вызывающий по-прежнему может нести ответственность за очистку соединения, потому что регистратор не удерживает его.

3) создайте третий класс «Inserter», который является одноразовым и принимает журнал и соединение в своем конструкторе. Устройство для вставки избавляется от соединения, когда оно удаляется; тогда вызывающий абонент отвечает за утилизацию устройства вставки.

person Eric Lippert    schedule 20.05.2010
comment
Это интересно, потому что большую часть времени я обнаружил, что если вы его создаете, ваша задача - избавиться от него. Я был бы более удивлен, увидев, что регистратор удаляет соединение, которое может использоваться или не использоваться другими регистраторами. Например, я давно не использовал SqlCommand напрямую, но, насколько я помню, он не удаляет соединение, которое вы ему передаете. - person Josh; 21.05.2010
comment
@Josh: Если хочешь поделиться, сделай вариант №2. Соединение просто не должно быть участником журнала. - person Eric Mickelsen; 21.05.2010
comment
@Josh: Сравните это с потоковыми читателями, которые сами распоряжаются потоком, который они читают. Важно то, чтобы контракт не был (1) сложным или (2) неожиданным. Я был бы удивлен, если бы обнаружил, что регистратор хранит ссылку на удаленную коллекцию; как мне узнать, что регистратор с этим покончил? - person Eric Lippert; 21.05.2010
comment
К сожалению, внесение этих улучшений выходит за рамки моей текущей задачи. Я хотел бы внести в это решение очень много таких изменений, но в настоящее время мне это не поручено. Однако это был лишь один из многих примеров. Некоторые из других случаев уже соответствуют вашему второму предложению. Учитывая все это, мне все еще хотелось бы узнать ваше мнение по поводу моего первоначального вопроса. - person Igby Largeman; 21.05.2010
comment
достаточно честно, но есть NetworkStream, который дает вам явную возможность в конструкторе относительно того, предоставляете ли вы ему право собственности на сокет. Мне нравится ваше предложение №2, но №1 кажется мне таким же расплывчатым. - person Josh; 21.05.2010

Я бы послушал аккуратного в тебе. Лично мне его путь больше нравится.

person Community    schedule 20.05.2010
comment
Потому что второй способ казался мне очень уродливым, и я очень внимательно отношусь к тому, как выглядит код. - person Josh; 21.05.2010
comment
Ладно, ладно, un -1 за честность. - person Eric Mickelsen; 21.05.2010
comment
Ничего страшного. Я подумал, что на этот вопрос нет правильного ответа, поэтому я отметил свою вики сообщества ответов. :) - person Josh; 21.05.2010

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

person Eric Mickelsen    schedule 20.05.2010
comment
Согласен, этот пример показывает плохой интерфейс, но пока оставим это в стороне. Я понимаю, что эти два пути эквивалентны, но есть ли у вас интуитивные предпочтения в том или ином виде? - person Igby Largeman; 21.05.2010
comment
@Charles: Моя интуиция подсказывает мне, что причина, по которой вы столкнулись с этой дилеммой, - это плохой исходный дизайн (сделать соединение членом). Если вы действительно не можете отменить этот выбор, я бы все равно не сделал ни того, ни другого. Я бы сделал что-то близкое к №1, но я бы поместил объект журнала полностью в область действия блока using, чтобы вы не могли попытаться использовать его после удаления соединения. - person Eric Mickelsen; 21.05.2010
comment
Да, в действительности работа программиста по найму такова, что вы сталкиваетесь с этой дилеммой каждый день и чаще всего не можете исправить основную проблему. Это факт, который я регулярно вижу, как очевидно опытные разработчики на SO не могут понять. Ваша точка зрения о перемещении всех ссылок на объект, потребляющий соединение, внутри блока using, чтобы предотвратить возможность его использования соединения после удаления, является хорошей мыслью. Спасибо за ваш вклад. - person Igby Largeman; 21.05.2010

Если log реализует IDisposable, сделайте второй выбор, поскольку фигурные скобки указаны явно. В некоторых случаях вы можете использовать несколько операторов using:

using (Graphics g = ...)
using (Pen p = new Pen ...)
using (Font f = new Font ...)
{



}

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

person Charlie Salts    schedule 20.05.2010
comment
log не реализует IDisposable. Имея это в виду, вы бы предпочли аккуратный первый выбор? - person Igby Largeman; 21.05.2010
comment
Тогда следуйте советам Эрика Липперта. В этой области он почти настоящий конь. - person Charlie Salts; 21.05.2010
comment
Предложения Эрика были бы замечательными, если бы я был тем, кто создавал классы, используя соединение, но он не предложил ничего, что касалось бы моего фактического вопроса. - person Igby Largeman; 21.05.2010
comment
Если OracleConnection реализует IDisposable, выберите последний вариант. - person Charlie Salts; 21.05.2010