Удаление объекта из того же объекта

#c# #dispose #fluent

#c# #удаление #свободно

Вопрос:

Я разрабатываю свободный API, и его использование примерно так:

 IUser user = work
                 .Timeout(TimeSpan.FromSeconds(5))
                 .WithRepository(c => c.Users)
                 .Do(r => r.LoadByUsername("matt"))
                 .Execute();
 

Итак, допустим, work это тип IUnitOfWork , но метод WithRepository(c => c.Users) возвращает вызываемый интерфейс IActionFlow<IUserRepository> , который является IDisposable .

Когда я вызываю Execute() и получаю конечный результат, я теряю ссылку на этот IActionFlow<IUserRepository> экземпляр, поэтому я не могу его удалить.

Каковы недостатки того, что экземпляр dipose сам зависит от Execute() метода?

Что-то вроде:

 public TResult Execute()
{
    // ...
    Dispose();
    return resu<
}
 

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

Комментарии:

1. Ну, это просто означает, что вы возвращаете удаленный объект, который действительно выглядит странно :). У плавных интерфейсов есть некоторые проблемы: ocramius.github.io/blog/fluent-interfaces-are-evil здесь есть целое обсуждение шаблона Dispose с плавными интерфейсами: davefancher.com/2015/06/14 /…

Ответ №1:

У вас может быть Using такой метод:

 public static TResult Using<TDisposable, TResult>(Func<TDisposable> factory,
    Func<TDisposable, TResult> fn) where TDisposable : IDisposable {
    using (var disposable = factory()) {
        return fn(disposable);
    }
}
 

Тогда ваш код будет выглядеть следующим образом:

 var user = Using(() => work.
    Timeout(TimeSpan.FromSeconds(5)).
    WithRepository(c => c.Users),
    repository => repository.Do(r => r.LoadByUsername("matt")).
        Execute());
 

Это позволит вашему API оставаться свободным, и в то же время вы будете удалять WithRepository его в тот же момент Execute , когда он будет завершен.

Комментарии:

1. Мне нравится продуманность решения, но ИМО это излишне.

Ответ №2:

Вариант 1:

Можете ли вы обернуть свой код внутри using блока, чтобы dispose вызывался автоматически,

 using(var repository = work.Timeout(TimeSpan.FromSeconds(5)).WithRepository(c => c.Users))
{
   IUser user = repository
                 .Do(r => r.LoadByUsername("matt"))
                 .Execute();

}
 

Таким образом, вашему Execute методу не нужно вызывать Dispose()

 public TResult Execute()
{
    // ...
    //Dispose();
    return resu<
}
 

Вариант 2:

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

Что-то вроде этого (может быть переработано дальше),

 using(var repository = work
                 .Timeout(TimeSpan.FromSeconds(5))
                 .WithRepository(c => c.Users)
                 .Do(r => r.LoadByUsername("matt"))
                 .Execute())
{

   IUser user = repository.Resu<
   //repository.Dispose();
}

//******

public TResult Result { get; set; }

public IActionFlow<IUserRepository> Execute()
{
    // ...
    //Dispose();
    this.Result = resu<
    return this;
}
 

Примечание: После вызова Dispose() метода сборка мусора может произойти на любом этапе в зависимости от доступных ресурсов сервера. Поэтому вызов этого внутри Execute метода может привести к неожиданным странным проблемам.

Комментарии:

1. Я могу, но это сделает мой API совсем не свободным

2. @MatiasCicero предоставил альтернативную идею, но должен провести ее дальнейший рефакторинг.

3. Или даже можно обернуть вариант 2 внутри using блока

Ответ №3:

Одноразовые объекты должны быть удалены из клиентского кода, один из побочных эффектов, который вы увидите из того, что вы делаете, заключается в том, что когда ваши потребители используют ваш API, они будут получать ошибки анализа кода (если они его используют, многие делают), например: «CA2213: одноразовые поля должны быть удалены» см. https://msdn.microsoft.com/en-us/library/ms182328.aspxдля получения подробной информации об этой ошибке.

Сказав вышеизложенное, вы не должны утилизировать вещи, которые абсолютно необходимы для утилизации в методе «Execute», возьмите эти сценарии:

Сценарий # 1

  1. У вас есть такие вещи, как открытые соединения, которые вы должны утилизировать, соединения открываются каким-то другим методом, кроме Execute.
  2. Во время выполнения метода «Do» возникает ошибка, dispose никогда не вызывается, поскольку execute никогда не вызывался, а открытое соединение оставалось открытым. Обратите внимание, что даже если на вашем компьютере никогда не произойдет сбой, это не гарантирует, что он не выйдет из строя при производстве из-за случайных событий, происходящих с машиной из другого потока.

PS операторы using и вся функциональность IDisposable созданы для решения таких ситуаций, заставляя хороших разработчиков распоряжаться независимо от обстоятельств.

Сценарий # 2

  1. Методы выполнения делают что-то одноразовое, например, открывают соединение, а затем делают что-то еще.

В этом сценарии вам не нужен метод dispose, вы можете просто удалить соединение в методе execute, обернув соединение в оператор using, например:

 public void Execute(object whatever){
    using (var conn = new Connection()){
        //method body
    }
}
 

Если у вас есть сценарий № 1, вы должны позволить клиенту использовать оператор using и должны будете терпеть его отсутствие (деструкторы не будут его вырезать), если у вас есть сценарий № 2, вам вообще не нужно dispose .

Ответ №4:

Мне кажется, что вы пытаетесь избавиться от репозитория.

Если вы хотите сохранить свой API без изменений, я бы посоветовал следующее:

 using(var repo = work.Timeout(TimeSpan.FromSeconds(5))
      .WithRepository(c => c.Users))
{
    IUser user = repo.Do(r => r.LoadByUsername("matt")).Execute();
    //Do whatever with user here if lazy loading.
}
 

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

Другим вариантом было бы создать класс-оболочку для результата, который поддерживает ссылку на репозиторий (например, ActionFlowExecution) и реализует IDisposable:

 public class ActionFlowExecution<TResult, TRepository> : IDisposable
{
    private TRepository _repository;
    internal ActionFlowExecution(TResult result, TRepository repository)
    {
        Result = resu<
        _repository = repository;
    }

    public TResult Result { get; private set; }

    public void Dispose()
    {
        if(_repository != null)
        {
            _repository.Dispose();
            _repository = null;
        }
    }
}
 

Затем вы можете вызвать свой API следующим образом:

 using(var execution = work
             .Timeout(TimeSpan.FromSeconds(5))
             .WithRepository(c => c.Users)
             .Do(r => r.LoadByUsername("matt"))
             .Execute())
{
    IUser user = execution.Resu<
    //Do whatever with user here 
}
 

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

Ответ №5:

Вы можете безопасно делать то, что упомянули, если не будете использовать этот объект позже. Единственная, но очень важная вещь здесь — это именование. Я бы вызвал метод ExecuteAndDispose . Другие варианты либо нарушают ваш fluent API, либо просто излишни, а также не просты в использовании.