Как правильно реализовать IDisposable
Я очень плохо знаком с C# и сталкиваюсь со следующим предупреждением:
Предупреждение CA1001 Реализуйте IDisposable в 'form_Inspection_Upload.d__4', потому что он создает члены следующих типов IDisposable: 'StringReader', 'WebServicesSoapClient'
Это является результатом перестройки с использованием VS Express 2015 для настольных компьютеров, ОС Windows 7. Я просмотрел различные публикации с похожими сообщениями, но не смог понять, как они применимы к коду, показанному ниже. В приведенном выше предупреждении говорится, что объект 'StringReader' ("a_string_reader") должен быть удален? Если так, то почему он не утилизируется после того, как его подпрограмма USING завершена?
Далее в том же блоке кода находится объект 'WebServicesSoapClient' ("ebws"). Насколько я могу сказать, он не имеет Dispose
метод, но может быть закрыт. Разве закрытие сервисного клиента - это не то же самое, что удаление его из существования? Видимо, нет, но я не уверен, как реализовать IDisposable
об этом, как показано на https://msdn.microsoft.com/library/ms182172.aspx.
Если это поможет, вот код. Часть, о которой идет речь, является третьим и последним блоком кода (важные части имеют комментарий // important line
.):
private async void button_Upload_Click(object sender, EventArgs e)
{
using (StringReader a_string_reader = new StringReader(pdf_doc_string)) // important line
{ do
{
a_line = a_string_reader.ReadLine();
} while (a_line != null);
}
eBridge.WebServicesSoapClient ebws = new eBridge.WebServicesSoapClient(); // important line
eBridge.FileUploadResponse upload_result = await ebws.FileUploadAsync(User, Password, Cabinet, b, File_Path, Index1_Program, Index2_Permit_Number, Index3_Name, Index4_Address, Index5_ZipCode, Index6_Doc_Date, Index7_Doc_Type);
ebws.Close(); // important line
}
}
1 ответ
Я наткнулся на статью Марка Симанна "Блог ploeh" "Как избавиться от членов от форм" ( http://blogs.msdn.com/b/ploeh/archive/2006/08/10/howtodisposemembersfromforms.aspx), в которой рассматривается проблема путь, который щелкнул для меня. Спасибо, Марк! Также очень полезным было то, что Хенк указал, что предупреждение относится к участникам.
ПОЖАЛУЙСТА, не стесняйтесь исправить или уточнить эту публикацию, так как мои знания C# и связанного языка очень новичок.
Краткий ответ - создать открытый класс для каждого объекта, нуждающегося в удалении, с добавлением ID подкласса к базовому классу объектов. Внутри класса создайте метод Dispose для объекта. Затем приведите этот элемент в форму.
Более подробное объяснение состоит в следующем:
- был создан общедоступный класс для веб-службы, включающий в себя IDisposable
. public class eBWS : eBridge.WebServicesSoapClient, IDisposable
{
public eBWS(<arguments>)
{
this.FileUploadExAsync(<arguments>);
}
public void Dispose() { }
}
В методе button_Upload_Click заменены
. using (eBridge.WebServicesSoapClient ebws = new eBridge.WebServicesSoapClient())
{
eBridge.FileUploadResponse upload_result = await ebws.FileUploadAsync(<arguments>);
textBox_Results.Text = upload_result.ToString();
}
с
. ebws = new eBWS(<arguments>);
Использовал аналогичный подход с объектом StringReader, но теперь сталкиваюсь с другой проблемой. Здесь не будем это решать.
В случае, если блог когда-либо исчезнет из сети, следует полный текст (еще раз, спасибо вам за статью Марка Симанна "ploeh blog" "Как избавиться от участников от форм"):
Авг 2006 5:22
19
Когда вы создаете новую форму (или в этом случае UserControl), Visual Studio создает ее как частичный класс, а сгенерированный дизайнером код помещается в файл *.Designer.cs. Еще одним фрагментом кода, который также входит в код конструктора, является переопределение System.ComponentModel.Component.Dispose(bool), которое содержит код для удаления формы и содержащихся в ней компонентов. Это всегда выглядит так:
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
Немного прискорбно, что этот метод переопределяется, потому что это затрудняет добавление вашей собственной логики удаления в форму, так как вы не можете переопределить метод еще раз в том же классе - помните, что код формы, которым вы управляете это только часть определения класса. Очевидно, что вы можете редактировать файл конструктора напрямую, но это не рекомендуется.
Тогда что вы можете сделать, если вам нужно выполнить какую-то дополнительную логику удаления в форме? Эта потребность может легко возникнуть, если одна из ваших переменных-членов реализует IDisposable, и в этом случае вы действительно должны избавиться от нее, когда удалится Форма. Представьте, что у вас есть следующий класс IDisposable:
public class MyDisposable : IDisposable
{
private string message_;
public MyDisposable(string message)
{
this.message_ = message;
}
#region IDisposable Members
public void Dispose()
{
MessageBox.Show(this.message_);
}
#endregion
}
Теперь вы хотите, чтобы в вашей форме была переменная-член MyDisposable, и вам нужно избавиться от нее, когда форма располагает:
public partial class MyForm : Form
{
private MyDisposable myDisposable_;
public MyForm()
{
InitializeComponent();
this.myDisposable_ =
new MyDisposable("Goodbye, World");
}
}
Когда вы запускаете приложение и закрываете форму, форма удаляется, но вы никогда не увидите прощального сообщения, потому что myDisposable_ не утилизируется. Метод Dispose в коде конструктора обрабатывает логику dispose для формы и ничего не знает о переменной-члене myDisposable_.
Однако он удаляет свой компонент-член, и, поскольку это IContainer, проблема будет решена, если вы добавите myDisposable_ в контейнер. К сожалению, IContainer принимает только экземпляры IComponent, и если вы не хотите внедрять IComponent в MyDisposable.
Часть контракта IComponent заключается в том, что реализация должна иметь конструктор по умолчанию, и по причинам разработки API вы можете не найти это приемлемым для вашего типа IDisposable - по некоторым причинам, я не думаю, что это приемлемое изменение для MyDisposable.
Однако вы можете добавить экземпляр этого небольшого класса в контейнер:
internal class Disposer : Component
{
private Action<bool> dispose_;
internal Disposer(Action<bool> disposeCallback)
{
this.dispose_ = disposeCallback;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this.dispose_(disposing);
}
}
Из вашего кода формы вам нужно соединить новый экземпляр Disposer с делегатом, который будет содержать реальную логику удаления. Когда используется в MyForm, теперь это выглядит так:
public partial class MyForm : Form
{
private MyDisposable myDisposable_;
public MyForm()
{
InitializeComponent();
this.myDisposable_ =
new MyDisposable("Goodbye, World");
this.components.Add(new Disposer(this.OnDispose));
}
private void OnDispose(bool disposing)
{
this.myDisposable_.Dispose();
}
}
Обратите внимание, что я добавил новый экземпляр Disposer, который указывает на новый метод OnDispose, который затем реализует всю логику удаления, не обработанную кодом конструктора. Теперь, когда я запускаю приложение и закрываю форму, я получаю ожидаемый MessageBox.
Этот метод обычно используется всякий раз, когда вам нужно избавиться от переменной-члена, но иначе не может, потому что код конструктора уже переопределяет метод Dispose. Однако я рекомендую этот подход только в том случае, если ваша переменная-член также не реализует IComponent.
Если у вас есть контроль над классом переменной-члена и вы не против иметь конструктор по умолчанию, я рекомендую вам вместо этого реализовать IComponent (самый простой способ сделать это - извлечь из System.ComponentModel.Component, как я это сделал с классом Disposer). Как правило, это даст вам более приятный опыт во время разработки, поскольку теперь вы можете просто перетащить компонент с панели инструментов на поверхность конструктора, и дизайнер (Visual Studio) автоматически добавит его в переменную-член компонентов, и, таким образом, он будет размещены вместе со всеми другими компонентами в контейнере.
Оба подхода также работают, если вы создаете UserControl, хотя там вам, вероятно, придется создавать экземпляр переменной-члена компонента самостоятельно.