Есть ли какое-либо влияние на использование оператора в статических методах? - PullRequest
0 голосов
/ 12 октября 2018

Не впитывая мнения с эстетической точки зрения, давайте сосредоточимся на том, каковы технические последствия следующего.

Я также большой поклонник использования методов выражения тела для простых методов и свойств.как метод цепочки.IMO они выглядят красиво и чисто без лишнего шума фигурных скобок (Да, я также использую F #).

Однако вы не можете использовать методы выражения тела для методов, содержащих оператор using.Не сосредотачиваясь на специфике синтаксиса ниже, что, если таковые имеются, будут технические последствия делать что-то как следующее:

class ...
{
  HttpClient client;

  public async Task SaveSomething(X value) =>
    Using
     .Disposable(await client.PostAsync("..", value))
     .Act(response => response.EnsureSuccessStatusCode());

  public async Task<X> GetSomething() =>
    await Using
     .Disposable(await client.GetAsync(".."))
     .Act(response => response.ReadAsAsync<X>());
}

Реализация статической оболочки:

static class Using
{
  public static DisposableAct<TDisposable> Disposable<TDisposable>(TDisposable disposable)
    where TDisposable : IDisposable => new DisposableAct<TDisposable>(disposable);
}

class DisposableAct<TDisposable> where TDisposable : IDisposable
{
  private readonly TDisposable disposable;

  public DisposableAct(TDisposable disposable) => this.disposable = disposable;

  public TResult Act<TResult>(Func<TDisposable, TResult> act)
  {
    using (disposable)
    {
      return act(disposable);
    }
  }
}

В отличие от:

class ...
{
  HttpClient client;

  public async Task SaveSomething(X value)
  {
    using (var response = await client.PostAsync("..", value))
    {
      response.EnsureSuccessStatusCode();
    }
  }

  public Task<X> GetSomething()
  {
    using (var response = await client.PostAsync("..", value))
    {
      return await response.ReadAsAsync<X>();
    }
  }
}

1 Ответ

0 голосов
/ 12 октября 2018

Существует множество различий между вашей оболочкой и кодом, который вы пытаетесь реплицировать.

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

  2. Если исключение произойдет до того, как вы вызовете Act, одноразовый ресурс будет утечкой.В другом коде нет такой возможности утечки одноразового ресурса.

  3. Если вы наберете Act несколько раз, одноразовый ресурс уже будет утилизирован.

  4. Если вы никогда не наберете Act на упаковке, то одноразовый ресурс никогда не будет утилизирован.Это в основном # 2, но если автор кода делает это неправильно.

  5. Вы создаете несколько дополнительных объектов, увеличивая нагрузку на память.Это и ваша одноразовая оболочка, и дополнительные async методы, то есть дополнительные конечные автоматы.

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

Итак, что касается исправления всех этих вещей,Первым делом было бы просто полностью удалить объект-обертку.Это создает многочисленные возможности для программиста просто сделать это неправильно (№ 3, № 4 и некоторые другие, о которых я не удосужился упомянуть), и является большой частью № 5.Вместо этого просто используйте статический метод, принимающий два аргумента, одноразовый ресурс и действие, которое нужно предпринять.Это дает вам контроль, необходимый для обеспечения того, чтобы всегда (или, по крайней мере, ближе к всегда) выполнялось правильно.

public static TResult UseDisposable<TDisposable, TResult>(TDisposable disposable, Func<TDisposable, TResult> function)
    where TDisposable : IDisposable
{
    using (disposable)
    {
        return function(disposable);
    }
}

Далее, если вы хотите поддерживать асинхронные методы, вам необходимоспециально для этого нужно перегрузить просто , в котором вы понимаете, что это асинхронный метод и обрабатываете его соответствующим образом.К счастью await позволяет легко писать.(Обратите внимание, что с технической точки зрения использование async здесь означает, что мы создаем конечный автомат, которого мы могли бы технически избежать, если бы мы делали это вручную. Если вы хотите избежать этого отличия от оригинала, вам нужно либо выполнить всерукой (что на удивление сложно, если вы хотите убедиться, что все правильные действия по обработке ошибок и отмене выполнены правильно). Если вы можете жить с добавленными выделениями, то код не слишком сложен, чем код, который вы 'мы пытаемся выполнить репликацию.

public static async Task<TResult> UseDisposable<TDisposable, TResult>(TDisposable disposable, Func<TDisposable, Task<TResult>> function)
    where TDisposable : IDisposable
{
    using (disposable)
    {
        return await function(disposable);
    }
}

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

public static void UseDisposable<TDisposable>(TDisposable disposable, Action<TDisposable> action)
    where TDisposable : IDisposable
{
    using (disposable)
    {
        action(disposable);
    }
}
public static async Task UseDisposable<TDisposable, TResult>(TDisposable disposable, Func<TDisposable, Task> action)
    where TDisposable : IDisposable
{
    using (disposable)
    {
        await action(disposable);
    }
}

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

public static TResult UseDisposable<TDisposable, TResult>(Func<TDisposable> disposableGenerator, Func<TDisposable, TResult> function)
    where TDisposable : IDisposable
{
    using (var disposable = disposableGenerator())
    {
        return function(disposable);
    }
}
public static async Task<TResult> UseDisposable<TDisposable, TResult>(Func<TDisposable> disposableGenerator, Func<TDisposable, Task<TResult>> function)
    where TDisposable : IDisposable
{
    using (var disposable = disposableGenerator())
    {
        return await function(disposable);
    }
}
public static void UseDisposable<TDisposable>(Func<TDisposable> disposableGenerator, Action<TDisposable> action)
    where TDisposable : IDisposable
{
    using (var disposable = disposableGenerator())
    {
        action(disposable);
    }
}
public static async Task UseDisposable<TDisposable, TResult>(Func<TDisposable> disposableGenerator, Func<TDisposable, Task> action)
    where TDisposable : IDisposable
{
    using (var disposable = disposableGenerator())
    {
        await action(disposable);
    }
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...