2016-11-30 3 views
4

Мы имеем следующие конструкции в нашем коде, используется для обеспечения конкретного ресурса утилизируется после использования:Есть ли способ сообщить компилятору, что конкретный блок кода всегда будет выполняться?

using (var disposableThing = DisposableThing.Begin()) 
{ 
    // do something 

    disposableThing.Finish(); // must always be called 
} 

Вот пример его использования:

List<int> ids; 

using (var disposableThing = DisposableThing.Begin()) 
{ 
    ids = disposableThing.GetSomeIds(); 

    disposableThing.Finish(); 
} 

DoSomethingElseWith(ids); 

Поскольку эта модель настолько распространена мы написали метод на DisposableThing инкапсулировать его:

static void ExecuteWithFinish(Action<DisposableThing> action) 
{ 
    using (var disposableThing = Begin()) 
    { 
     action(disposableThing); 

     disposableThing.Finish(); 
    } 
} 

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

// #4 
List<int> ids; 

DisposableThing.ExecuteWithFinish(disposableThing => 
{ 
    ids = disposableThing.GetSomeIds(); 
}); 

DoSomethingElseWith(ids); // compiler error "Use of unassigned local variable 'ids'" 

Но компилятор отказывается компилировать этот код, поскольку он не имеет возможности узнать, что ids всегда будет присвоен после ExecuteWithFinish завершения (или брошено исключение, которое позволит предотвратить выполнение DoSomethingElseWith так или иначе).

  • Я знаю, что я мог бы добавить перегрузку ExecuteWithFinish, которая возвращает значения из сдавших в Func, что некрасиво.
  • Я знаю, что я мог бы подкласса DisposableThing и переопределить его метод Dispose для вызова Finish, который является более чистым, опрятным и более быстрым способом, чем создание делегата каждый раз (это, вероятно, то, что я в конечном итоге сделаю).

Но для моего собственного назидания и в духе «что, если», можно ли проинформировать или даже обмануть компилятор, разрешив код в №4 как написанный?

редактировать: Да, я знаю, что я мог бы написать List<int> ids = null; и обойти эту проблему полностью, но (а) я бы предпочел, чтобы не выполнять ненужные назначения (б) Я хотел бы, чтобы изменить код как можно меньше ,

+5

Хочу отметить, что комментарий для 'disposableThing. Finish() 'says" всегда должен быть вызван ". Это верно, даже если «сделать что-то» вызывает исключение? Потому что если это так, вы должны инкапсулировать всю вещь 'Begin()' 'Finish()' 'Dispose()' в отдельный класс, который вы можете управлять с помощью одного 'use'. –

+0

Просто общее замечание. Почему бы не использовать шаблон «IDisposable», который кажется более очевидным, чем повторное использование механизма с использованием метода «Готово»? Поместите код из «Готово» в свой метод «Dispose» (или просто вызовите 'Finish' из' Dispose'). Таким образом, вы избегаете ошибки компилятора и получаете поддержку от языка, потому что код в 'Finish' будет автоматически вызываться в конце инструкции using. –

+0

@MatthewWatson: Из документации там я бы предположил, что 'Finish' будет вызываться в' Dispose' в любом случае. Я имею в виду, что вряд ли лучший вариант использования «IDisposable». В конце концов, потоки также автоматически закрываются при удалении, и в этом случае вам не нужно называть 'Close' вручную ... – Joey

ответ

0

Простой нуль-задание будет избежать предупреждения компилятора, как объяснено в документации compiler error CS0165:

List<int> ids = null; 
+0

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

5

Я бы взять другой подход здесь.

Я собираюсь сделать предположение, что по какой-то причине у вас должен быть метод Finish(), который всегда должен вызываться до Dispose(), который также должен всегда вызываться.

Это может быть опрометчивое предположение, и это скорее задает вопрос: почему бы вам не включить функциональность Finish() в Dispose()? Однако ...

Во-первых, создать интерфейс для инкапсуляции одноразовую вещь с помощью метода Finish():

public interface IDisposableThingWithFinish : IDisposable 
{ 
    void Finish(); 
} 

и изменить свой DisposableThing класс так, что он реализует IDisposableThingWithFinish.

Тогда вы могли бы написать одноразовый класс, который инкапсулирует призывающую Finish(), а затем Dispose() так:

public sealed class DisposingFinisher : IDisposable 
{ 
    readonly IDisposableThingWithFinish _item; 

    public Disposing(IDisposableThingWithFinish item) 
    { 
     if (item == null) 
      throw new ArgumentNullException(nameof(item)); 

     _item = item; 
    } 

    public void Dispose() 
    { 
     try 
     { 
      _item.Finish(); 
     } 

     finally 
     { 
      _item.Dispose(); 
     } 
    } 
} 

Вы могли бы использовать Finisher так:

using (var disposableThing = new DisposingFinisher(DisposableThing.Begin())) 
{ 
    // Do something. 
}