dotSITE
Шаблоны проектирования Work in Murano Software. Вопросы/Ответы
новости материалы решения форумы группы настройки/о проекте
Логин/Регистрация
Логин:
Пароль:
Запомнить вас:
Регистрация
Забыли пароль?

Комментарии

Общие шаблоны разработки

Цикл статей

Общие шаблоны разработки

В этом разделе приведены рекомендации по реализации общих шаблонов разработки в библиотеках классов.

 

Применение методов Finalize и Dispose для очистки неуправляемых ресурсов

Экземпляры классов часто инкапсулируют элемент управления поверх ресурсов, таких как описатель окна (HWND), соединения с базами данных и т.д., которые не управляются средой выполнения. Поэтому вы должны обеспечить как явный, так и неявный способ освобождения этих ресурсов. Неявный контроль обеспечьте реализацией в объекте protected метода Finalize (синтаксис деструктора в C# и управляемые расширения (Managed Extensions) для С++). Сборщик мусора вызывает этот метод в определенный момент после того, как больше не существует ни одной действительной ссылки на объект.

В некоторых случаях может понадобится предоставить программистам, использующим объект, возможность явно освобождать эти внешние ресурсы до того, как это сделает сборщик мусора. В случае, если внешние ресурсы недостаточны или дороги, лучшей производительности можно достигнуть явно освобождая ресурсы, которые больше не используются. Чтобы предоставить явный контроль, реализуйте метод Dispose, поставляемый интерфейсом IDisposable. Потребитель объекта должен вызвать этот метод, когда использование объекта закончено. Метод Dispose может быть вызван даже тогда, когда существуют и другие ссылки на объект.

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

Более подробно об использовании методов Finalize и Dispose для очистки неуправляемых ресурсов смотри в разделе MSDN Programming for Garbage Collection. Следующий пример иллюстрирует основную схему разработки для реализации метода Dispose.

[C#]
// Design pattern for a base class.
public class Base: IDisposable
{
   //Implement IDisposable.
   public void Dispose() 
   {
     Dispose(true);
      GC.SuppressFinalize(this); 
   }
 
   protected virtual void Dispose(bool disposing) 
   {
      if (disposing) 
      {
         // Free other state (managed objects).
      }
      // Free your own state (unmanaged objects).
      // Set large fields to null.
   }
 
   // Use C# destructor syntax for finalization code.
   ~Base()
   {
      // Simply call Dispose(false).
      Dispose (false);
   }
   
// Design pattern for a derived class.
public class Derived: Base
{   
   protected override void Dispose(bool disposing) 
   {
      if (disposing) 
      {
         // Release managed resources.
      }
      // Release unmanaged resources.
      // Set large fields to null.
      // Call Dispose on your base class.
      base.Dispose(disposing);
   }
   // The derived class does not have a Finalize method
   // or a Dispose method with parameters because it inherits
   // them from the base class.
}

Более подробный пример, иллюстрирующий схему разработки для реализации методов Finalize и Dispose, смотри в разделе MSDN Implementing a Dispose Method.

Изменение имени метода Dispose

Иногда домен-специфическое имя больше подходит, чем Dispose. Например, инкапсуляция файла может захотеть использовать имя метода Close. В этом случае реализуйте метод Dispose как private и создайте public метод Close, который вызывает метод Dispose. Следующий пример иллюстрирует эту схему. Вы можете заменить имя Close именем метода, которое соответствует вашему домену.

[C#]
// Do not make this method virtual.
// A derived class should not be allowed
// to override this method.
public void Close()
{
   // Call the Dispose method with no parameters.
   Dispose();
}

Finalize

Далее приведены основные рекомендации по применению метода Finalize:

  • Применяйте метод Finalize только в тех объектах, которые требуют финализации. При использовании этого метода возникают некоторые потери производительности.
  • Если вам необходим метод Finalize, вы должны рассмотреть реализацию IDisposable, чтобы дать возможность пользователям вашего класса предотвратить затраты на активизацию метода Finalize.
  • Не делайте метод Finalize более видимым. Он должен быть protected, а не public.
  • Метод Finalize объекта должен освобождать любые внешние ресурсы, принадлежащие объекту. Более того, метод Finalize должен освобождать только те ресурсы, которые удерживаются объектом. Метод Finalize не должен обращаться к другим объектам.
  • Не делайте прямых вызовов метода Finalize в объектах, кроме объектов базового класса.
  • Вызывайте метод base.Finalize из метода Finalize объекта.

Примечание:  Метод Finalize базового класса вызывается автоматически с синтаксисом деструктора C# и Управляемых расширений (Managed Extensions) для C++.

Dispose

Далее приведены основные рекомендации по применению метода Dispose:

  • Применяйте схему разработки ликвидации в типе, инкапсулирующем ресурсы, которые нуждаются в явном освобождении. Пользователи могут освобождать внешние ресурсы, вызывая public метод Dispose.
  • Применяйте схему разработки ликвидации в базовом типе, обычно имеющем наследуемые типы, которые удерживают ресурсы, даже если базовый класс не делает этого. Наличие в базовом типе закрытого метода обычно свидетельствует о необходимости реализации Dispose. В таких случаях не реализуйте метод Finalize в базовом типе. Finalize должен быть реализован в любых унаследованных типах, представляющих ресурсы, которые нуждаются в очистке.
  • Освобождайте все имеющиеся в распоряжении типа ресурсы в методе Dispose.
  • После того, как метод Dispose был вызван в экземпляре, вызовите метод GC.SuppressFinalize, чтобы не допустить запуск метода Finalize. Исключением из этого правила является редкая ситуация, когда работа должна быть сделана в методе Finalize, который не покрывается методом Dispose.
  • Вызывайте метод Dispose базового класса, если он реализует IDisposable.
  • Не предполагайте, что метод Dispose будет вызван. Неуправляемые ресурсы, принадлежащие типу, также должны быть освобождены в методе Finalize на  случай, если метод Dispose не вызывается.
  • Формируйте ObjectDisposedException, когда ресурсы уже освобождены. Если вы решили перераспределить ресурсы после того, как объект освобожден, убедитесь что вы вызываете метод GC.ReRegisterForFinalize.
  • Передавайте вызовы Dispose через иерархию базовых типов. Метод Dispose должен освободить все ресурсы, удерживаемые данным объектом и любым другим объектом, принадлежащим данному. Например, вы можете создать такой объект, как TextReader, который поддерживает объекты Stream и Encoding. Оба эти объекта создаются TextReader без ведома пользователя. Более того, и Stream, и Encoding могут запрашивать внешние ресурсы. Когда вы вызываете метод Dispose в TextReader, Dispose вызывается и в Stream и Encoding, заставляя их освободить внешние ресурсы.
  • Объект не может использоваться после того, как был вызван его метод Dispose. Повторное создание объекта, который уже был ликвидирован, сложно реализовать.
  • Предоставьте возможность многократного вызова метода Dispose без формирования исключительной ситуации. После первого вызова метод не должен ничего делать.

 

Реализация метода Equals

Информация, касающаяся реализации оператора равенства (==), представлена в разделе Рекомендации по применению метода Equals и оператора равенства (==).

  • Переопределите метод GetHashCode, чтобы предоставить возможность типу корректно работать в хэш-таблице.
  • Не формируйте исключительную ситуацию в реализации метода Equals. Вместо этого возвращайте false для null аргумента.
  • Следуйте контракту, определенному в методе Object.Equals, как показано ниже:

-                       x.Equals(x) возвращает true.

-                       x.Equals(y) возвращает то же значение, что и y.Equals(x).

-                       (x.Equals(y) && y.Equals(z)) возвращает true, если и только если x.Equals(z) возвращает true.

-                       Успешные вызовы x.Equals(y) возвращают одно и то же значение до тех пор, пока не модифицированы объекты, на которые ссылаются x и y.

-                       x.Equals(null) возвращает false.

  • Для некоторых видов объектов желательно применять критерий Equals для обозначения равенства значений вместо ссылочного равенства. Такая реализация Equals возвращает true, если два объекта имеют одно и то же значение, даже если они не являются одним и тем же экземпляром. Определение того, что составляет значение объекта, является задачей разработчика типа, но обычно некоторые или все данные сохраняются в переменных экземпляра объекта. Например, значение строки основывается на ее символах; метод Equals класса String возвращает true для любых двух экземпляров строки, которые содержат именно эти символы в том же порядке.
  • Когда метод Equals базового класса предоставляет равенство значений, переопределение Equals в наследуемом классе должно вызывать унаследованную реализацию Equals.
  • Если вы используете язык программирования, который поддерживает перезагрузку операторов, и решаете перезагрузить оператор равенства (==) для определенного типа, этот тип должен переопределить метод Equals. При такой реализации метод Equals должен возвращать такие же результаты, что и оператор равенства. Выполнение данных рекомендации поможет обеспечить совместимость кода библиотеки классов, использующего Equals (например, ArrayList и Hashtable), с кодом приложения, который использует оператор равенства.
  • При реализации типа значения вы должны рассмотреть переопределение метода Equals, для того чтобы добиться увеличения производительности через реализацию метода Equals, используемую по умолчанию, в System.ValueType. Если вы переопределяете Equals, и язык программирования поддерживает перезагрузку операторов, вы должны перезагрузить оператор равенства для своего типа значения.
  • При реализации ссылочных типов вы должны рассмотреть переопределение метода Equals в ссылочном типе, если ваш тип похож на базовые типы, такие как Point, String, BigNumber и т.д. В большинстве ссылочных типов не надо переопределять оператор равенства, даже если переопределяется Equals. Однако, если реализуется ссылочный тип, который будет иметь симантики значения, например, тип комплексного числа, оператор равенства должен быть переопределен.
  • При реализации интерфейса IComparable в данном типе его метод Equals а должен быть переопределен.

Примеры

Реализация метода Equals

В следующем примере содержатся два вызова реализации метода Equals, применяемой по умолчанию:

[C#]
using System;
class SampleClass 
{
   public static void Main() 
   {
      Object obj1 = new Object();
      Object obj2 = new Object();
      Console.WriteLine(obj1.Equals(obj2));
      obj1 = obj2; 
      Console.WriteLine(obj1.Equals(obj2)); 
   }
}

В результате получим следующее:

False
True

Переопределение метода Equals

В следующем примере представлены класс Point, который переопределяет метод Equals, чтобы обеспечить равенство значений, и класс Point3D, унаследованный от Point. Поскольку переопределение метода Equals в классе Point является первым в цепи наследования, метод Equals базового класса (который наследуется от Object и проверяет ссылочное равенство) не вызывается. Однако Point3D.Equals вызывает Point.Equals, потому что класс Point реализовывает метод Equals так, что обеспечивается равенство значений.

[C#]
using System;
class Point: object 
{
   int x, y;
   public override bool Equals(Object obj) 
   {
      // Check for null values and compare run-time types.
      if (obj == null || GetType() != obj.GetType()) 
         return false;
      Point p = (Point)obj;
      return (x == p.x) && (y == p.y);
   }
   public override int GetHashCode() 
   {
      return x ^ y;
   }
}
 
class Point3D: Point 
{
   int z;
   public override bool Equals(Object obj) 
   {
      return base.Equals(obj) && z == ((Point3D)obj).z;
   }
   public override int GetHashCode() 
   {
      return base.GetHashCode() ^ z;
   }
}

Метод Point.Equals проверяет наличие аргумента obj и то, что он ссылается на экземпляр того же типа, что и данный объект. Если одна из этих проверок не удается, метод возвращает значение false. Метод Equals использует метод Object.GetType для того, чтобы определить идентичность типов времени выполнения этих двух объектов. Обратите внимание, что typeof (TypeOf в Visual Basic) здесь не используется, потому что он возвращает статический тип. Если вместо этого метод использовал проверку в форме obj is Point, в результате проверки будет возвращено значение true в случаях, когда obj является экземпляром класса, наследуемого от Point, даже если obj и текущий экземпляр не одного типа времени выполнения. Установив, что эти два объекта имеют один и тот же тип, метод приводит obj к типу Point и возвращает результат сравнения переменных экземпляра двух объектов.

В Point3D.Equals унаследованный метод Equals вызывается первым. Метод Equals проверяет, существует ли obj, является ли obj экземпляром того же класса, что и данный объект, и совпадают ли переменные экземпляра. Только когда унаследованный Equals возвращает значение true, метод сравнивает переменные экземпляра, представленные в дочернем классе. В частности, приведение к Point3D не производится до тех пор, пока не будет определено, что obj имеет тип Point3D или является классом, унаследованным от Point3D.

Использование метода Equals для сравнения переменных экземпляра

В предыдущем примере оператор равенства (==) используется для сравнения переменных отдельного экземпляра. В некоторых случаях для сравнения переменных экземпляра в реализации Equals лучше использовать метод Equals, как показано в следующем примере.

[C#]
using System;
class Rectangle 
{
   Point a, b;
   public override bool Equals(Object obj) 
   {
      if (obj == null || GetType() != obj.GetType()) return false;
      Rectangle r = (Rectangle)obj;
      // Use Equals to compare instance variables.
      return a.Equals(r.a) && b.Equals(r.b);
   }
   public override int GetHashCode() 
   {
      return a.GetHashCode() ^ b.GetHashCode();
   }
}

Перезагрузка оператора равенства (==) и метода Equals

В некоторых языках программирования, таких как С#, поддерживается перезагрузка оператора. Когда тип перезагружает оператор ==, чтобы обеспечить те же функциональные возможности, должен быть переопределен и метод Equals. Это обычно достигается путем написания метода Equals в терминах перезагруженного оператора равенства (==), как показано в следующем примере.[C#]

public struct Complex 
{
   double re, im;
   public override bool Equals(Object obj) 
   {
      return obj is Complex && this == (Complex)obj;
   }
   public override int GetHashCode() 
   {
      return re.GetHashCode() ^ im.GetHashCode();
   }
   public static bool operator ==(Complex x, Complex y) 
   {
      return x.re == y.re && x.im == y.im;
   }
   public static bool operator !=(Complex x, Complex y) 
   {
      return !(x == y);
   }
}
 

Т.к. Complex является структурой, известно, что ни один класс не будет наследоваться от Complex. Поэтому методу Equals не надо сравнивать результаты GetType для каждого объекта. Вместо этого он использует оператор is, чтобы проверить тип параметра obj.

 

Использование функции обратного вызова

Делегаты, интерфейсы и события дают вам возможность предоставить функциональные возможности обратного вызова. Каждый тип имеет свои собственные характеристики использования, которые делают их более пригодными для отдельных ситуаций.

События

Используйте события, если истинны следующие утверждения:

  • Метод открыто используется для функции обратного вызова, обычно через отдельные методы Add и Remove.
  • Обычно более, чем один объект, запрашивают нотификацию события.
  • Вы хотите, чтобы конечный пользователь имел возможность запросто добавлять приемник в нотификацию в визуальном дизайнере.

Делегаты

Используйте делегаты, если истинно следующее:

  • Вы хотите иметь указатель функции в стиле языка С
  • Вы хотите иметь простую функцию обратного вызова.
  • Вы хотите чтобы регистрация происходила в вызове или во время построения, а не через отдельный метод Add.

Интерфейсы

Используйте интерфейсы, если функция обратного вызова требует комплексного поведения.

 

Использование ожиданий (Time-Out)

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

Ожидания должны принять форму параметра вызова метода, как показано ниже.

[C#]
server.PerformOperation(timeout);

Ожидания могут использоваться, как свойство серверного класса, как показано ниже.

[C#]
server.Timeout = timeout;
server.PerformOperation();

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

Исторически сложилось, что ожидания представляются целыми числами. В этом случае сложно определить единицы измерения ожидания, и трудно перевести единицы времени в миллисекунды, которые обычно используются.

Лучшим подходом является использование в качестве типа ожидания структуры TimeSpan. Применение TimeSpan решает проблемы с целочисленными ожиданиями, о которых шла речь ранее. Далее приведен пример того, как использовать ожидание типа TimeSpan.

[C#]
public class Server
{
   void PerformOperation(TimeSpan timeout)
   {
      // Insert code for the method here.
   } 
}
 
public class TestClass
{
   public Server server = new Server();
   server.PerformOperation(new TimeSpan(0,15,0));
}

Когда задано TimeSpan(0), если операция не завершается немедленно, метод формирует исключительную ситуацию. Если ожидание задано TimeSpan.MaxValue, операция будет ожидать постоянно, как будто ожидание не задано. Не требуется, чтобы серверный класс поддерживал какое-то из этих значений, но он должен формировать исключительную ситуацию InvalidArgumentException в случае, если ожиданию задается значение, не поддерживаемое классом.

Если время ожидания закончено и сформировалась исключительная ситуация, серверный класс должен отменить эту операцию.

Если используется ожидание, применяемое по умолчанию, серверный класс должен включать статическое свойство defaultTimeout, которое используется, если пользователь не задал ожидания. В следующем примере в код включено статическое свойство OperationTimeout типа TimeSpan, которое возвращает defaultTimeout.

[C#]
class Server
{
   TimeSpan defaultTimeout = new TimeSpan(1000); 
 
   void PerformOperation()
   {
      this.PerformOperation(OperationTimeout);
   }
 
   void PerformOperation(TimeSpan timeout)
   {
      // Insert code here.
   }
 
   TimeSpan OperationTimeout
   {
      get
      {
         return defaultTimeout;
      }
   }
}

Типы, которые не могут обеспечить разрешения, установленного в TimeSpan, должны округлять значение ожидания до ближайшего промежутка, который может быть обеспечен. Например, тип, который может обеспечить разрешение в одну секунду, должен округлять к ближайшей секунде. Исключением из этого правила является округление в нуль. В таком случае ожидание должно округляться до минимально возможного значения. Предотвращение округления в нуль предупреждает возникновение циклов активного ожидания, когда нулевое ожидание приводит к 100% использованию мощности процессора

Вместо того, чтобы возвращать ошибку кода при окончании периода ожидания, рекомендуется формировать исключительную ситуацию. Истечение периода ожидания означает, что операция не может быть успешно завершена и поэтому должна рассматриваться и обрабатываться, как любая другая ошибка времени выполнения. Более подробно смотри в разделе Рекомендации по формированию и обработке ошибок.

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

[C#]
void OnReceiveCompleted(Object sender, ReceiveAsyncEventArgs asyncResult)
{
   MessageQueue queue = (MessageQueue) sender;
   // The following code will throw an exception
   // if BeginReceive has timed out.
   Message message = queue.EndReceive(asyncResult.AsyncResult);
   Console.WriteLine("Message: " + (string)message.Body);
queue.BeginReceive(new TimeSpan(1,0,0));
}

Дополнительная информация представлена в разделе Рекомендации по асинхронному программированию.


Контакт Реклама на сайте Спонсорам Веб мастерам

Лицензионное соглашение - © 2000-2010 dotSITE
Хостинг .NET предоставлен PARKING.RU
Поддержку сайта осуществляет Murano Software Inc., Offshore software development