Одной из возможных причин утечек памяти в .Net являются события. Предположим, что где-то в программе объект мы подписывается на событие другого долгоживущего объекта. После подписки в объекте-генераторе события (а точнее внутри делегата события) будет храниться ссылка на объект-подписчик. Теперь, даже если не останется внутри программы других ссылок на объект-подписчик, его всё равно защитит от сборщика мусора «жёсткая» ссылка, хранящаяся в объекте-генераторе. И если своевременно не отписаться от события, то объект-подписчик может оставаться «живым» ещё достаточно долго (пока «живёт» объект-генератор). В случае, если объект подпишется на статическое событие, то из памяти он не удалится до завершения работы приложения.
Для того, чтобы не тратить время на добавление и отладку кода по своевременной отписке от событий, я разработал вспомогательный метод-расширитель, генерирующий для обычного делегата его «слабую» версию, которая не хранит в себе жёсткую ссылку на объект, а использует механизм слабых ссылок (weak references). Используя «слабую» версию делегата во время подписки на событие, объект-генератор события получит только слабую ссылку на объект-подписчик. Поэтому, даже если потом и не отписаться от события, объект-подписчик не будет удерживаться в памяти объектом-генератором.

Пример


Без использования «слабых» делегатов



//Подписка на событие
publisher.PublisherEvent += subscriber.Handler; //Handler - делегат

//А потом где-то в другом месте отдписка от события (если этого не сделать, то publisher будет удерживать в памяти subscriber)
publisher.PublisherEvent -= subscriber.Handler;


* This source code was highlighted with Source Code Highlighter.

С использованием «слабых»
делегатов


publisher.PublisherEvent += (HandlerType)subscriber.Handler.AsWeak(); //Отписываться от события теперь не обязательно.

* This source code was highlighted with Source Code Highlighter.

Исходный код


public static class WeakDelegateHelper
  {
    public static Delegate AsWeak(this Delegate del)
    {
      if (del.Target == null)
        return del;
      var weakTarget = new WeakReference(del.Target);
      ParameterExpression[] args = (from p in del.Method.GetParameters()
                     select Expression.Parameter(p.ParameterType, p.Name)).ToArray();
      LambdaExpression expression = Expression.Lambda
        (
          del.GetType(),
          Expression.Condition(Expression.Property(Expression.Constant(weakTarget), "IsAlive"),
                     Expression.Call(
                       Expression.Convert(
                         Expression.Property(Expression.Constant(weakTarget), "Target"),
                         del.Target.GetType()), del.Method, args),
                     Expression.Default(del.Method.ReturnType)
            ),
          args
        );
      return expression.Compile();
    }
  }

* This source code was highlighted with Source Code Highlighter.

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

Отправить комментарий