Поделиться через


Удаление объектов

Дата последнего изменения: 8 апреля 2010 г.

Применимо к: SharePoint Foundation 2010

В этой статье
Введение в использование удаляемых объектов SharePoint
Поиск неправильно удаляемых объектов
Приемы написания кода для обеспечения удаления объектов
Объекты SPSite
Объекты SPWeb
Другие объекты, требующие удаления
Заключение

Введение в использование удаляемых объектов SharePoint

Объекты в объектных моделях Microsoft SharePoint Foundation 2010 и Microsoft SharePoint Server 2010 служат в качестве интерфейса для работы с данными SharePoint Foundation. Разработчики часто вызывают объектную модель для чтения или записи данных в хранилищах данных SharePoint Foundation 2010 и SharePoint Server 2010.

Объектные модели SharePoint Foundation 2010 и SharePoint Server 2010 содержат объекты, реализующие интерфейс IDisposable. Необходимо принять меры предосторожности при использовании этих объектов в Microsoft .NET Framework, чтобы избежать их долговременного хранения в памяти.

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

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

  • Частый перезапуск пула приложений SharePoint Foundation, особенно во время пиковых нагрузок

  • Сбои приложений, отображающиеся в отладчике как повреждение кучи

  • Использование большого объема памяти рабочими процессами служб IIS

  • Низкая производительность системы и приложений

Данная статья служит в качестве руководства по выполнению требуемых процедур для обработки и удаления объектов SharePoint, реализующих интерфейс IDispose. Проблемы, описанные в данной статье, также выявляются средством проверки удаления объектов SharePoint (Возможно, на английском языке), доступной для загрузки бесплатной программой, которая проверяет сборки на наличие способов написания кода, приводящих к утечке памяти из-за неправильной обработки и удаления объектов SharePoint.

Почему необходимо удалять объекты

Некоторые объекты SharePoint Foundation, особенно объекты класса SPSite и SPWeb, создаются как управляемые объекты. Однако эти объекты используют неуправляемый код и память для выполнения основной части их работы. Управляемая часть объекта много меньше, чем неуправляемая часть. Так как меньшая управляемая часть не создает нехватку памяти, видимую для сборщика мусора, сборщик мусора не освобождает память своевременно от объекта. Использование объектом большого количества неуправляемой памяти может вызвать некоторое неприемлемое поведение, описанное выше. При вызове приложений, работающих с объектами IDisposable в SharePoint Foundation, необходимо удалять эти объекты после завершения использования их приложением. Нельзя полагаться на сборщик мусора и автоматическое освобождение объектов из памяти.

Поиск неправильно удаляемых объектов

Ниже перечислены вопросы, которые помогут определить потенциальное наличие неправильно удаляемых объектов:

  1. Часто ли перезапускается пул приложений, особенно при большой нагрузке (подразумевается, что пул приложений настроен на перезапуск при достижении порогового значения памяти)?

    Пороговое значение памяти должно быть от 800 МБ до 1,5 ГБ, при наличии минимум 2 ГБ ОЗУ. Перезапуск пула приложений при значении, близком к 1 ГБ, дает лучшие результаты, но рекомендуется провести экспериментальное определение того, какое значение лучше работает в имеющейся среде. Если параметр перезапуска слишком мал, в системе будут возникать проблемы с производительностью из-за частого перезапуска пула приложений. Если параметр слишком велик, в системе будут возникать проблемы с производительностью из-за подкачки страниц, фрагментации памяти и других проблем.

  2. Наблюдается ли плохая работа системы, особенно при больших нагрузках?

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

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

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

  4. Используются ли в системе настраиваемые или сторонние веб-части или настраиваемые приложения?

    Возможно, эти веб-части не удаляют объекты SharePoint, подразумевая, что сборщик мусора выполняет эту функцию автоматически. Однако это верно не во всех случаях.

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

Если сайты проявляют какое-либо необычное поведение, описанное выше, можно определить, является ли причиной нехватка памяти из-за неправильно удаляемых объектов, проверив журналы ULS (доступные в папке C:\Program Files\Common Files\microsoft shared\Web Server Extensions\14\LOGS) на записи, связанные с объектом SPRequest. Каждый экземпляр SPSite и SPWeb содержит ссылку на объект SPRequest, который в свою очередь содержит ссылку на неуправляемый объект COM, обрабатывающий связь с сервером баз данных. SharePoint Foundation отслеживает число объектов SPRequest, которые существуют в каждом определенном потоке и параллельных потоках и добавляет полезные записи в журналы в трех следующих сценариях:

  • Полное число объектов SPRequest превышает настраиваемый порог.

  • Объект SPRequest продолжает существовать после завершения потока.

  • Объект SPRequest был удален из кучи сборщиком мусора.

Первый сценарий встречается наиболее часто, особенно если сайт использует значение порога по умолчанию для девяти объектов SPRequest. Если число объектов SPRequest превышает порог, в журналах ULS появляется следующая запись:

"Potentially excessive number of SPRequest objects (number of objects) currently unreleased on thread number of thread. Ensure that this object or its parent (such as an SPWeb or SPSite object) is being properly disposed. Allocation Id for this object: {GUID}" (Потенциально избыточное число объектов SPRequest (число объектов) не удалено в настоящее время в потоке номер потока. Убедитесь, что этот объект или его родительский объект (например, объект SPWeb или SPSite) удаляется правильно. Идентификатор выделения для этого объекта: {GUID})

Лучшее значение порога зависит от свойств сайта и приложений, работающих на нем. Если сайты испытывают проблемы с производительностью, необходимо проверить журналы ULS установки, чтобы понять, сколько объектов SPRequest создается приложениями сайта. Это поможет определить, создают ли сайты и приложения слишком много объектов SPRequest. Даже если неправильное удаление объектов не является причиной проблем производительности, может потребоваться переделать сайты или настраиваемые приложения сайтов, чтобы уменьшить общее потребление памяти, вызванное избыточным числом объектов SPRequest.

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

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings

LocalSPRequestWarnCount = требуемое значение порога

После определения того, что неправильное удаление объектов может быть причиной большого числа объектов SPRequest и увеличения потребления памяти сайтами без необходимости, можно найти определенные экземпляры с неправильным удалением, просмотрев следующие две записи. Оба сообщения указывают на случаи неэффективного использования памяти из-за неправильного удаления объектов SharePoint, и оба случая зависят от числа и состояния объектов SPRequest в одном потоке:

  • "Объект SPRequest не был удален перед завершением этого потока. Чтобы избежать неэффективного использования системных ресурсов, удалите этот объект или его родительский объект (например, SPSite или SPWeb), как только использование объекта будет завершено. Этот объект будет удален. Идентификатор выделения: {GUID} Чтобы определить, где был выделен объект, создайте подраздел реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings. Затем создайте новый параметр DWORD с именем SPRequestStackTrace и значением 1 в этом разделе".

    Это сообщение указывает, что объект SPRequest был удален из-за того, что он существовал после завершения потока.

  • "An SPRequest object was reclaimed by the garbage collector instead of being explicitly freed. To avoid wasting system resources, dispose this object or its parent (such as an SPSite or SPWeb) as soon as you are done using it. Allocation Id: {GUID} To determine where this object was allocated, create a registry key at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings. Then create a new DWORD named SPRequestStackTrace with the value 1 under this key." (Объект SPRequest был освобожден из памяти сборщиком мусора вместо явного освобождения. Чтобы избежать расходования системных ресурсов, удалите этот объект или его родительский объект (например, SPSite или SPWeb) после завершения его использования. Идентификатор выделения: {GUID} Чтобы определить, где этот объект был размещен, создайте раздел реестра по адресу HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings. Затем создайте новый параметр DWORD с именем SPRequestStackTrace и значением 1 в этом разделе.)

    Это сообщение указывает, что сборщик мусора удалил объект SPRequest.

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

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\HeapSettings SPRequestStackTrace = 1

Этот параметр подраздела обеспечивает то, что трассировка стека изначального выделения SPRequest (которое происходит при создании объектов SPSite или SPWeb) будет добавлена в журналы при возникновении этого предупреждения.

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

Приемы написания кода для обеспечения удаления объектов

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

  • Метод Dispose

  • Оператор using

  • Блоки try, catch и finally

Использование методов Dispose или Close

Методы Dispose и Close для объекта SPWeb и SPSite работают одинаково. Метод Dispose вызывает метод Close объекта. Рекомендуется вызывать метод Dispose вместо метода Close, так как объекты SPWeb и SPSite реализуют интерфейс IDisposable и стандартный сборщик мусора .NET Framework вызывает метод Dispose для освобождения из памяти всех ресурсов, связанных с объектом.

Оператор using

Можно автоматически удалять объекты SharePoint, реализующие интерфейс IDisposable, с помощью оператора using в Microsoft Visual C# и Visual Basic.

В следующем коде приведен пример.

String str;

using(SPSite oSPsite = new SPSite("https://server"))
{
  using(SPWeb oSPWeb = oSPSite.OpenWeb())
   {
       str = oSPWeb.Title;
       str = oSPWeb.Url;
   }
}  
Dim str As String

Using oSPsite As New SPSite("https://server")
  Using oSPWeb As SPWeb = oSPSite.OpenWeb()
     str = oSPWeb.Title
     str = oSPWeb.Url
  End Using
End Using

Использование оператора using может существенно упростить код. Как отмечено в справочнике по C# (Оператор using), среда выполнения CLR преобразует операторы using в блоки try and finally, и все объекты, реализующие интерфейс IDisposable, удаляются. Однако во многих случаях операторы using не рекомендуются, или они должны использоваться с некоторыми предосторожностями и пониманием того, что делает среда выполнения. В следующем примере кода показан один случай, где может потребоваться не позволять среде выполнения создавать блок finally и удалять объекты. В этом случае SPContext возвращает объект SPWeb.

// Do not do this. Dispose() is automatically called on SPWeb. 
using( SPWeb web = SPControl.GetContextWeb(HttpContext.Current)) { ... }
' Do not do this. Dispose() is automatically called on SPWeb. 
Using web As SPWeb = SPControl.GetContextWeb(HttpContext.Current)
    '.......
End Using

Объекты SPContext управляются платформой SharePoint и не должны явно удаляться в коде. Это верно также для объектов SPSite и SPWeb, возвращаемых SPContext.Site, SPContext.Current.Site, SPContext.Web и SPContext.Current.Web.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_220.

Необходимо принять меры предосторожности и знать действия среды выполнения при комбинировании вызовов объектной модели SharePoint в одной строке. Утечки, возникающие при данном сценарии, являются наиболее трудными для поиска.

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

void CombiningCallsLeak()
{
    using (SPWeb web = new SPSite(SPContext.Current.Web.Url).OpenWeb())
    {
        // ... New SPSite will be leaked.
    } // SPWeb object web.Dispose() automatically called.
}
Private Sub CombiningCallsLeak()
    Using web As SPWeb = New SPSite(SPContext.Current.Web.Url).OpenWeb()
        ' ... New SPSite will be leaked.
    End Using ' SPWeb object web.Dispose() automatically called.
End Sub

Эту проблему можно устранить вложением одного оператора using в другой.

void CombiningCallsBestPractice()
{
    using (SPSite siteCollection = new SPSite(SPContext.Current.Web.Url))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
        // Perform operations on site.
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub CombiningCallsBestPractice()
    Using siteCollection As New SPSite(SPContext.Current.Web.Url)
        Using web As SPWeb = siteCollection.OpenWeb()
            ' Perform operations on site.
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Если не выполняются какие-либо операции с объектом SPSite, можно написать это более выразительно, как в следующем примере.

void CombiningCallsBestPractice()
{
    using (SPSite siteCollection = new SPSite(SPContext.Current.Web.Url))
    using (SPWeb web = siteCollection.OpenWeb())
        {
        // Perform operations on site.
        } // SPWeb object web.Dispose() automatically called; SPSite object 
          // siteCollection.Dispose() automatically called.
}
Private Sub CombiningCallsBestPractice()
    Using siteCollection As New SPSite(SPContext.Current.Web.Url)
        Using web As SPWeb = siteCollection.OpenWeb()
            ' Perform operations on site.
        End Using ' SPWeb object web.Dispose() automatically called; SPSite object
    End Using
    ' siteCollection.Dispose() automatically called.
End Sub

В других случаях необходимо создать собственные блоки try, catch и finally. Наиболее очевидные примеры — это сценарии, где требуется обработать исключения и поэтому они должны включать блок catch. В следующем разделе предоставлены рекомендации о том, когда и как использовать блоки try, catch и finally.

Блоки try, catch и finally

Использование блоков try, catch и finally имеет смысл, когда требуется обработать исключения. Любой код в блоке try/catch должен иметь управляющий оператор finally, который обеспечивает удаление объектов, реализующих IDisposable. Обратите внимание, что в следующем примере кода необходимо заполнить блок catch кодом, обрабатывающим исключение. Никогда не оставляйте блок catch пустым. Также обратите внимание на рекомендацию проверки на значение null перед удалением.

String str;
SPSite oSPSite = null;
SPWeb oSPWeb = null;

try
{
   oSPSite = new SPSite("https://server");
   oSPWeb = oSPSite.OpenWeb(..);

   str = oSPWeb.Title;
}
catch(Exception e)
{
   // Handle exception, log exception, etc.
}
finally
{
   if (oSPWeb != null)
     oSPWeb.Dispose();

   if (oSPSite != null)
      oSPSite.Dispose();
}
Dim str As String
Dim oSPSite As SPSite = Nothing
Dim oSPWeb As SPWeb = Nothing

Try
   oSPSite = New SPSite("https://server")
   oSPWeb = oSPSite.OpenWeb(..)

   str = oSPWeb.Title
Catch e As Exception
' Handle exception, log exception, etc.
Finally
   If oSPWeb IsNot Nothing Then
 oSPWeb.Dispose()
   End If

   If oSPSite IsNot Nothing Then
  oSPSite.Dispose()
   End If
End Try

Блоки Try и finally или оператор using могут потребоваться, чтобы избежать потенциальных утечек при создании удаляемого объекта в блоке foreach, как показано в следующем примере кода.

public static void SPSiteCollectionForEachBestPractice()
{
     string sUrl = "http://spvm";
 
      using (SPSite siteCollectionOuter = new SPSite(sUrl))
     {
         SPWebApplication webApp = siteCollectionOuter.WebApplication;
         SPSiteCollection siteCollections = webApp.Sites;

                  SPSite siteCollectionInner = null;
                  foreach (siteCollectionInner in siteCollections)
             {
                      try  // Should be first statement after foreach.
                      {
                          Console.WriteLine(siteCollectionInner.Url);
                          // Exception occurs here.
                      }
                      finally
                      {
                          if(siteCollectionInner != null)
                          siteCollectionInner.Dispose();
                      }
             }
     } // SPSite object siteCollectionOuter.Dispose() automatically called.
 }
Public Shared Sub SPSiteCollectionForEachBestPractice()
    Dim sUrl As String = "http://spvm"

    Using siteCollectionOuter As New SPSite(sUrl)
        Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites

        Dim siteCollectionInner As SPSite = Nothing
        For Each siteCollectionInner In siteCollections
            Try ' Should be first statement after foreach.
                Console.WriteLine(siteCollectionInner.Url)
                ' Exception occurs here.
            Finally
                If siteCollectionInner IsNot Nothing Then
                    siteCollectionInner.Dispose()
                End If
            End Try
        Next
    End Using
End Sub ' SPSite object siteCollectionOuter.Dispose() automatically called.

Response.Redirect с блоками try, catch и finally и операторами using

Блок finally выполняется после вызовов Response.Redirect в блоке try. В итоге Response.Redirect создает исключение ThreadAbortException. При возникновении данного исключения среда выполнения выполняет все блоки finally перед завершением потока. Однако так как блок finally может выполнять неограниченное вычисление или отменять ThreadAbortException, поток не обязательно завершится. Поэтому перед любым перенаправление или передачей обработки необходимо удалить объекты. Если код должен выполнить перенаправление, реализуйте это способом, подобным следующему примеру кода.

String str;
SPSite oSPSite = null;
SPWeb oSPWeb = null;

try
{
   oSPSite = new SPSite("https://server");
   oSPWeb = oSPSite.OpenWeb(..);

   str = oSPWeb.Title;
   if(bDoRedirection)
   {
       if (oSPWeb != null)
          oSPWeb.Dispose();
    
       if (oSPSite != null)
          oSPSite.Dispose();

       Response.Redirect("newpage.aspx");
   }
}
catch(Exception e)
{
}
finally
{
   if (oSPWeb != null)
     oSPWeb.Dispose();

   if (oSPSite != null)
      oSPSite.Dispose();
}
Dim str As String
Dim oSPSite As SPSite = Nothing
Dim oSPWeb As SPWeb = Nothing

Try
   oSPSite = New SPSite("https://server")
   oSPWeb = oSPSite.OpenWeb(..)

   str = oSPWeb.Title
   If bDoRedirection Then
         If oSPWeb IsNot Nothing Then
                 oSPWeb.Dispose()
         End If
         If oSPSite IsNot Nothing Then
              oSPSite.Dispose()
         End If

         Response.Redirect("newpage.aspx")
   End If
Catch e As Exception
Finally
   If oSPWeb IsNot Nothing Then
         oSPWeb.Dispose()
   End If

   If oSPSite IsNot Nothing Then
         oSPSite.Dispose()
   End If
End Try

Так как оператор using дает команду среде выполнения создать блок finally, то при использовании Response.Redirect в операторе using убедитесь, что объекты удаляются правильно. В следующем примере кода показывается, как это можно сделать.

using (SPSite oSPSite = new SPSite("https://server"))
using (SPWeb oSPWeb = oSPSite.OpenWeb(..))
{
    if (bDoRedirection)
        Response.Redirect("newpage.aspx");
}
Using oSPSite As New SPSite("https://server")
    Using oSPWeb As SPWeb = oSPSite.OpenWeb(..)
        If bDoRedirection Then
            Response.Redirect("newpage.aspx")
        End If
    End Using
End Using

Рекомендации по уменьшению долговременного хранения объектов

Можно уменьшить долговременное хранение объектов SharePoint, следуя следующим общим рекомендациям.

  • При создании объекта с помощью оператора new убедитесь, что создающее приложение удаляет объект.

    ПримечаниеПримечание

    Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_110.

    Рекомендуемый прием написания кода № 1

    Явное удаление

    void CreatingSPSiteExplicitDisposeNoLeak()
    {
        SPSite siteCollection = null;
        try
        {
            siteCollection = new SPSite("http://moss");
        }
        finally
        {
            if (siteCollection != null)
                siteCollection.Dispose();
        }
    }
    
    Private Sub CreatingSPSiteExplicitDisposeNoLeak()
        Dim siteCollection As SPSite = Nothing
        Try
            siteCollection = New SPSite("http://moss")
        Finally
            If siteCollection IsNot Nothing Then
                siteCollection.Dispose()
            End If
        End Try
    End Sub
    

    Рекомендуемый прием написания кода № 2

    Автоматическое удаление

    CreatingSPSiteWithAutomaticDisposeNoLeak()
    {
        using (SPSite siteCollection = new SPSite("http://moss"))
        {
        } // SPSite object siteCollection.Dispose() is called automatically.
    }
    
    CreatingSPSiteWithAutomaticDisposeNoLeak()
    {
        using (SPSite siteCollection = new SPSite("http://moss"))
        {
        } // SPSite object siteCollection.Dispose() is called automatically.
    }
    
  • Удаляйте объекты, созданные методами SharePoint, которые возвращают другие объекты SPWeb (такие как OpenWeb()).

    ПримечаниеПримечание

    Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_120.

    Рекомендуемый прием программирования

    void OpenWebNoLeak()
    {
        using (SPSite siteCollection = new SPSite("http://moss"))
        {
            using (SPWeb web = siteCollection.OpenWeb())
            {
            } // SPWeb object web.Dispose() automatically called.
        }  // SPSite object siteCollection.Dispose() automatically called.
    }
    
    Private Sub OpenWebNoLeak()
            Using siteCollection As New SPSite("http://moss")
                    Using web As SPWeb = siteCollection.OpenWeb()
                    End Using ' SPWeb object web.Dispose() automatically called.
            End Using ' SPSite object siteCollection.Dispose() automatically called.
    End Sub
    
  • Не используйте какие-либо объекты SPRequest (а также любые объекты, содержащие ссылку на объект SPRequest) в разных потоках. Любые приемы программирования, в которых используется объект SPRequest в двух или более потоках или создается объект SPRequest в одном потоке и удаляется в другом, не поддерживаются. Это значит, что нельзя сохранять какой-либо объект, содержащий ссылку на объект SPRequest в статической переменной. Поэтому не сохраняйте объекты SharePoint, реализующие IDisposable (такие как SPWeb или SPSite) а статических переменных.

Объекты SPSite

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

В общем случае каждый раз при использовании вызывающим приложением нового конструктора SPSite (с любой сигнатурой) нужно вызывать метод Dispose(), когда завершается использование объекта. Если объект SPSite получен от GetContextSite(), вызывающее приложение не должно удалять объект. Так как объекты SPWeb и SPSite содержат внутренний список, происходящий таким образом, удаление объекта может вызвать непредвиденное поведение объектной модели SharePoint. Внутренне SharePoint Foundation перечисляет этот список после завершения страницы, чтобы правильно удалить объекты.

Класс SPSiteCollection

В этом разделе описываются методы, свойства или операторы объекта SPSiteCollection которые требуют, чтобы возвращенный объект SPSite был закрыт после обеспечения доступа.

Метод SPSiteCollection.Add

Метод SPSiteCollection.Add создает и возвращает новый объект SPSite. Необходимо удалить любой объект SPSite, возвращенный методом SPSiteCollection.Add.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_240.

Пример неправильного написания кода

void SPSiteCollectionAddLeak()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;
    SPSite siteCollection = siteCollections.Add("sites/myNewSiteCollection", "DOMAIN\\User", 
      "roger.lamb@litwareinc.com");
    // SPSite siteCollection leak.
}
Private Sub SPSiteCollectionAddLeak()
        Dim webApp As SPWebApplication = New SPSite("http://moss").WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites
        Dim siteCollection As SPSite = siteCollections.Add("sites/myNewSiteCollection", "DOMAIN\User", "roger.lamb@litwareinc.com")
        ' SPSite siteCollection leak.
End Sub

Рекомендуемый прием написания кода

void SPSiteCollectionAddNoLeak()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;
    using (SPSite siteCollection = siteCollections.Add("sites/myNewSiteCollection", "DOMAIN\\User", 
      "roger.lamb@litwareinc.com"))
    {
    } // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub SPSiteCollectionAddNoLeak()
      Dim webApp As SPWebApplication = New SPSite("http://moss").WebApplication
      Dim siteCollections As SPSiteCollection = webApp.Sites
      Using siteCollection As SPSite = siteCollections.Add("sites/myNewSiteCollection", "DOMAIN\User", "roger.lamb@litwareinc.com")
      End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Оператор индекса SPSiteCollection [ ]

Оператор индекса SPSiteCollection [] возвращает новый объект SPSite для каждого доступа. Экземпляр SPSite создается даже в том случае, если уже было обращение к данному объекту. В следующих примерах кода показано ошибочное удаление объекта SPSite.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_230.

Пример неправильного кодирования № 1

Использование оператора индекса

void SPSiteCollectionIndexerLeak()
{
    using (SPSite siteCollectionOuter = new SPSite("http://moss"))
    {
        SPWebApplication webApp = siteCollectionOuter.WebApplication;
        SPSiteCollection siteCollections = webApp.Sites;

        SPSite siteCollectionInner = siteCollections[0];
        // SPSite siteCollectionInner leak. 
    } // SPSite object siteCollectionOuter.Dispose() automatically called.
}
Private Sub SPSiteCollectionIndexerLeak()
    Using siteCollectionOuter As New SPSite("http://moss")
        Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites

        Dim siteCollectionInner As SPSite = siteCollections(0)
        ' SPSite siteCollectionInner leak. 
    End Using ' SPSite object siteCollectionOuter.Dispose() automatically called.
End Sub

Пример неправильного кодирования № 2

Использование цикла foreach

void SPSiteCollectionForEachLeak()
{
    using (SPSite siteCollectionOuter = new SPSite("http://moss"))
    {
        SPWebApplication webApp = siteCollectionOuter.WebApplication;
        SPSiteCollection siteCollections = webApp.Sites;

        foreach (SPSite siteCollectionInner in siteCollections)
        {
            // SPSite siteCollectionInner leak.
        }
    } // SPSite object siteCollectionOuter.Dispose() automatically called.
}
Private Sub SPSiteCollectionForEachLeak()
    Using siteCollectionOuter As New SPSite("http://moss")
        Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites

        For Each siteCollectionInner As SPSite In siteCollections
            ' SPSite siteCollectionInner leak.
        Next siteCollectionInner
    End Using ' SPSite object siteCollectionOuter.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода № 1

Использование оператора индекса

void SPSiteCollectionIndexerNoLeak()
{
    using (SPSite siteCollectionOuter = new SPSite("http://moss"))
    {
        SPSite siteCollectionInner = null;
        try
        {
            SPWebApplication webApp = siteCollectionOuter.WebApplication;
            SPSiteCollection siteCollections = webApp.Sites;

            siteCollectionInner = siteCollections[0];
        }
        finally
        {
            if (siteCollectionInner != null)
                siteCollectionInner.Dispose();
        }
    } // SPSite object siteCollectionOuter.Dispose() automatically called.
}
Private Sub SPSiteCollectionIndexerNoLeak()
    Using siteCollectionOuter As New SPSite("http://moss")
        Dim siteCollectionInner As SPSite = Nothing
        Try
            Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
            Dim siteCollections As SPSiteCollection = webApp.Sites

            siteCollectionInner = siteCollections(0)
        Finally
            If siteCollectionInner IsNot Nothing Then
                siteCollectionInner.Dispose()
            End If
        End Try
    End Using ' SPSite object siteCollectionOuter.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода № 2

Использование цикла foreach

void SPSiteCollectionForEachNoLeak()
{
    using (SPSite siteCollectionOuter = new SPSite("http://yoursite”))
    {
        SPWebApplication webApp = siteCollectionOuter.WebApplication;
        SPSiteCollection siteCollections = webApp.Sites;

        foreach (SPSite siteCollectionInner in siteCollections)
        {
            try
            {
                // ...
            }
            finally
            {
                if(siteCollectionInner != null)
                    siteCollectionInner.Dispose();
            }
        }
    } // SPSite object siteCollectionOuter.Dispose() automatically called.
}
Private Sub SPSiteCollectionForEachNoLeak()

    Using siteCollectionOuter As SPSite = New SPSite("http://yoursite")
        Dim webApp As SPWebApplication = siteCollectionOuter.WebApplication
        Dim siteCollections As SPSiteCollection = webApp.Sites
        For Each siteCollectionInner As SPSite In siteCollections
            Try
                ' ...
            Finally
                If siteCollectionInner IsNot Nothing Then
                    siteCollectionInner.Dispose()
                End If
            End Try
        Next
    End Using
End Sub

Свойство SPSite.AllWebs (SPWebCollection)

В данном разделе описаны методы, свойства и операторы коллекции свойств AllWebs, которые требуют закрытия объекта SPWeb после осуществления доступа.

Метод SPSite.AllWebs.Add

Метод SPSite.AllWebs.Add создает и возвращает объект SPWeb. Нужно удалить все объекты SPWeb, возвращаемые из SPSite.AllWebs.Add.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_150.

Пример неправильного написания кода

void AllWebsAddLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        SPWeb web = siteCollection.AllWebs.Add("site-relative URL");
        // SPWeb object leaked.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsAddLeak()
    Using siteCollection As New SPSite("http://moss")
        Dim web As SPWeb = siteCollection.AllWebs.Add("site-relative URL")
        ' SPWeb object leaked.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода

void AllWebsAddNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.AllWebs.Add("site-relative URL"))
        {
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsAddNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.AllWebs.Add("site-relative URL")
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Метод SPWebCollection.Add

Метод SPWebCollection.Add создает и возвращает объект SPWeb, который требуется удалить.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_200.

Пример неправильного написания кода

void SPWebCollectionAddLeak(string strWebUrl)
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            SPWebCollection webCollection = siteCollection.AllWebs; // No AllWebs leak just getting reference.
            SPWeb innerWeb = webCollection.Add(strWebUrl);  // Must dispose innerWeb.
            // innerWeb leak.
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub SPWebCollectionAddLeak(ByVal strWebUrl As String)
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            Dim webCollection As SPWebCollection = siteCollection.AllWebs ' No AllWebs leak just getting reference.
            Dim innerWeb As SPWeb = webCollection.Add(strWebUrl) ' Must dispose innerWeb.
            ' innerWeb leak.
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода

void SPWebCollectionAddNoLeak(string strWebUrl)
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            SPWebCollection webCollection = siteCollection.AllWebs; // No AllWebs leak just getting reference.
            using (SPWeb innerWeb = webCollection.Add(strWebUrl))
            {
                //...
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub SPWebCollectionAddNoLeak(ByVal strWebUrl As String)
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            Dim webCollection As SPWebCollection = siteCollection.AllWebs ' No AllWebs leak just getting reference.
            Using innerWeb As SPWeb = webCollection.Add(strWebUrl)
                '...
            End Using
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Оператор индекса SPSite.AllWebs [ ]

Оператор индекса SPSite.AllWebs [] возвращает новый экземпляр SPWeb при каждом обращении к нему. Объект создается во время операции индексирования, даже если к этому объекту уже было обращение. Если закрытие выполнено неправильно, следующий пример кода оставляет объект SPWeb в сборщике мусора .NET Framework.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_130.

Пример неправильного написания кода

void AllWebsForEachLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in siteCollection.AllWebs)
            {
                // Explicitly dispose here to avoid out of memory leaks with large number of SPWeb objects.
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsForEachLeak()
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            For Each innerWeb As SPWeb In siteCollection.AllWebs
                ' Explicitly dispose here to avoid out of memory leaks with large number of SPWeb objects.
            Next
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода № 1

Использование цикла foreach

void AllWebsForEachNoLeakOrMemoryOOM()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in siteCollection.AllWebs)
            {
                try
                {
                    // ...
                }
                finally
                {
                    if(innerWeb != null)
                        innerWeb.Dispose();
                }
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsForEachNoLeakOrMemoryOOM()
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            For Each innerWeb As SPWeb In siteCollection.AllWebs
                Try
                    ' ...
                Finally
                    If innerWeb IsNot Nothing Then
                        innerWeb.Dispose()
                    End If
                End Try
            Next
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода № 2

Использование оператора индекса

void AllWebsIndexerNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.AllWebs[0])
        {
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub AllWebsIndexerNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.AllWebs(0)
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Методы SPSite.OpenWeb и SPSite. SelfServiceCreateSite

Методы OpenWeb() и SelfServiceCreateSite (со всеми сигнатурами) объекта SPSite создают объект SPWeb и возвращают его вызывающему приложению. Этот новый объект не сохраняется в объекте SPSite и не удаляется где-либо в классе SPSite. По этой причине необходимо удалить все объекты, созданные с помощью этих методов.

Пример неправильного написания кода

void OpenWebLeak()
{
    using (SPWeb web = new SPSite(SPContext.Current.Web.Url).OpenWeb())
    {
        // SPSite leaked !
    } // SPWeb object web.Dispose() automatically called.
}
Private Sub OpenWebLeak()
    Using web As SPWeb = New SPSite(SPContext.Current.Web.Url).OpenWeb()
        ' SPSite leaked !
    End Using ' SPWeb object web.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода

void OpenWebNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub OpenWebNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Свойство SPSite.RootWeb

В предыдущем руководстве указано, что вызывающее приложение должно удалять свойство SPSite.RootWeb перед удалением объекта SPSite, который использует его. Это больше не является официальной рекомендацией. Удаление обрабатывается автоматически в SharePoint Foundation и SharePoint Server. Также свойства SPSiteLockIssue, Owner и SecondaryContact использовали свойство RootWeb внутренне. В обновленном руководстве для RootWeb более не рекомендуется вызывать метод Dispose для свойства SPSite.RootWeb, когда какое-либо из этих свойств используется.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_140.

Рекомендуемый прием написания кода

public void RootWebBestPractice()
{
    // New SPSite.
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        SPWeb rootWeb1 = siteCollection.RootWeb;
        // No explicit rootWeb1 dispose required.
    }  // siteCollection automatically disposed by implementing using().
    // rootWeb1 will be Disposed by SPSite.

    // SPContext and SPControl
    SPWeb rootWeb2 = SPContext.Current.Site.RootWeb;
    // Also would apply to SPControl.GetContextSite(Context);
    // No explicit rootWeb2 dispose required because it is obtained from SPContext.Current.Site.
}
Public Sub RootWebBestPractice()
    ' New SPSite.
    Using siteCollection As New SPSite("http://moss")
        Dim rootWeb1 As SPWeb = siteCollection.RootWeb
        ' No explicit rootWeb1 dispose required.
    End Using ' siteCollection automatically disposed by implementing using().
    ' rootWeb1 will be Disposed by SPSite.

    ' SPContext and SPControl
    Dim rootWeb2 As SPWeb = SPContext.Current.Site.RootWeb
    ' Also would apply to SPControl.GetContextSite(Context);
    ' No explicit rootWeb2 dispose required because it is obtained from SPContext.Current.Site.
End Sub

Microsoft.Office.Server.UserProfiles.PersonalSite (только Office SharePoint Server 2007)

Microsoft.Office.Server.UserProfiles.PersonalSite возвращает объект SPSite, который должен быть удален.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_400.

Пример неправильного написания кода

void PersonalSiteLeak()
{
    // Open a site collection.
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
        UserProfile profile = profileManager.GetUserProfile("domain\\username");
        SPSite personalSite = profile.PersonalSite;    // Will leak.
    }
}
Private Sub PersonalSiteLeak()
    ' Open a site collection.
    Using siteCollection As New SPSite("http://moss")
        Dim profileManager As New UserProfileManager(ServerContext.GetContext(siteCollection))
        Dim profile As UserProfile = profileManager.GetUserProfile("domain\username")
        Dim personalSite As SPSite = profile.PersonalSite ' Will leak.
    End Using
End Sub

Рекомендуемый прием написания кода

void PersonalSiteNoLeak()
{
    // Open a site collection.
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
        UserProfile profile = profileManager.GetUserProfile("domain\\username");
        using (SPSite personalSite = profile.PersonalSite)
        {
            // ...
        }
    }
}
Private Sub PersonalSiteNoLeak()
    ' Open a site collection.
    Using siteCollection As New SPSite("http://moss")
        Dim profileManager As New UserProfileManager(ServerContext.GetContext(siteCollection))
        Dim profile As UserProfile = profileManager.GetUserProfile("domain\username")
        Using personalSite As SPSite = profile.PersonalSite
            ' ...
        End Using
    End Using
End Sub

В другом крайнем случае UserProfiles.PersonalSite утечки, как показано в следующем примере кода.

void PersonalSiteLeak()
{
    // Open a site collection.
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
        UserProfile profile = profileManager.GetUserProfile("domain\\username");
        SPSite personalSite = profile.PersonalSite;    // Will leak.
    }
}
Private Sub PersonalSiteLeak()
    ' Open a site collection.
    Using siteCollection As New SPSite("http://moss")
        Dim profileManager As New UserProfileManager(ServerContext.GetContext(siteCollection))
        Dim profile As UserProfile = profileManager.GetUserProfile("domain\username")
        Dim personalSite As SPSite = profile.PersonalSite ' Will leak.
    End Using
End Sub

Можно разрешить этот тип утечек с помощью паттерна, показанного в следующем примере кода.

void PersonalSiteNoLeak()
{
    // Open a site collection
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
        UserProfile profile = profileManager.GetUserProfile("domain\\username");
        using (SPSite personalSite = profile.PersonalSite)
        {
            // ...
        }
    }
}
Private Sub PersonalSiteNoLeak()
    ' Open a site collection
    Using siteCollection As New SPSite("http://moss")
        Dim profileManager As New UserProfileManager(ServerContext.GetContext(siteCollection))
        Dim profile As UserProfile = profileManager.GetUserProfile("domain\username")
        Using personalSite As SPSite = profile.PersonalSite
            ' ...
        End Using
    End Using
End Sub

Также обратите внимание, что можно увеличить производительность (и избежать создания объекта SPSite) с помощью получения объекта PersonalSite из ProfileLoader, как показано в следующем примере кода.

UserProfile myProfile = ProfileLoader.GetProfileLoader().GetUserProfile();
using (SPSite personalSite = myProfile.PersonalSite)
{
     // ...
}
Dim myProfile As UserProfile = ProfileLoader.GetProfileLoader().GetUserProfile()
Using personalSite As SPSite = myProfile.PersonalSite
       ' ...
End Using

Также при создании веб-части для личного сайта можно использовать экземпляр PersonalSite, который не требуется удалять.

IPersonalPage currentMySitePage = this.Page as IPersonalPage;
if (currentMySitePage != null && !currentMySitePage.IsProfileError)
{
     SPSite personalSite = currentMySitePage.PersonalSite; // Will not leak.
     // ...
}
Dim currentMySitePage As IPersonalPage = TryCast(Me.Page, IPersonalPage)
If currentMySitePage IsNot Nothing AndAlso (Not currentMySitePage.IsProfileError) Then
         Dim personalSite As SPSite = currentMySitePage.PersonalSite ' Will not leak.
         ' ...
End If

Объекты SPWeb

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

Свойство SPWeb.ParentWeb

Обновленное руководство

В предыдущем руководстве указано, что вызывающее приложение должно удалять свойство SPWeb.ParentWeb. Это более не является официальной рекомендацией. Удаление обрабатывается автоматически в SharePoint Foundation и SharePoint Server.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_170.

Рекомендуемый прием написания кода

using (SPSite site = new SPSite("https://localhost")) 
{
    using (SPWeb web = site.OpenWeb())
    {
        SPList list = web.Lists["Announcements"];
        SPWeb parentWeb = list.ParentWeb; //No explicit dispose required.
    }
}
Using site As New SPSite("https://localhost")
      Using web As SPWeb = site.OpenWeb()
            Dim list As SPList = web.Lists("Announcements")
            Dim parentWeb As SPWeb = list.ParentWeb 'No explicit dispose required.
      End Using
End Using

Свойство SPWeb.Webs

В этом разделе описываются методы, свойства или операторы в коллекции свойств Webs, требующие удаления объекта SPWeb после осуществления доступа.

SPWeb.Webs

Свойство SPWeb.Webs возвращает объект SPWebCollection. Объекты SPWeb в этой коллекции должны быть удалены.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_180.

Пример неправильного написания кода

void WebsLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in outerWeb.Webs)
            {
                // SPWeb innerWeb leak.
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub WebsLeak()
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            For Each innerWeb As SPWeb In outerWeb.Webs
                ' SPWeb innerWeb leak.
            Next
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода

void WebsNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in outerWeb.Webs)
            {
                try // Should be first statement after foreach.
                {
                    // ...
                }
                finally
                {
                    if(innerWeb != null)
                        innerWeb.Dispose();
                }
            }
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub WebsNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using outerWeb As SPWeb = siteCollection.OpenWeb()
            For Each innerWeb As SPWeb In outerWeb.Webs
                Try ' Should be first statement after foreach.
                    ' ...
                Finally
                    If innerWeb IsNot Nothing Then
                        innerWeb.Dispose()
                    End If
                End Try
            Next
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

SPWeb.Webs.Add

Метод SPWeb.Webs.Add (или SPWebCollection.Add) создает и возвращает новый объект SPWeb. Необходимо удалить все объекты SPWeb, возвращаемые вызовом этого метода.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_190.

Пример неправильного написания кода

void WebsAddLeak(string strWebUrl)
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPWeb addedWeb = web.Webs.Add(strWebUrl);   // Will leak.

        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub WebsAddLeak(ByVal strWebUrl As String)
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim addedWeb As SPWeb = web.Webs.Add(strWebUrl) ' Will leak.

        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода

void WebsAddNoLeak(string strWebUrl)
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            using (SPWeb addedWeb = web.Webs.Add(strWebUrl))
            {
                //..
            }

        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub WebsAddNoLeak(ByVal strWebUrl As String)
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Using addedWeb As SPWeb = web.Webs.Add(strWebUrl)
                '..
            End Using

        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Оператор индекса SPWeb.Webs[]

Оператор индекса SPWeb.Webs[] возвращает новый объект SPWeb при каждом обращении. Объект SPWeb создается вызовом метода OpenWeb, даже если к этому объекту уже было обращение. Следующие примеры кода приводят к долговременному хранению этих объектов в памяти, используемой платформой .NET Framework.

Пример неправильного кодирования № 1

Использование цикла For

int i;

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

oSPWeb = oSPSite.OpenWeb();

for(i = 0;i < oSPWeb.Webs.Count;i++)
{
   oSPWeb2 = oSPWeb.Webs[i];
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
}
Dim i As Integer

Dim oSPWeb, oSPWeb2 As SPWeb
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

oSPWeb = oSPSite.OpenWeb()

For i = 0 To oSPWeb.Webs.Count - 1
   oSPWeb2 = oSPWeb.Webs(i)
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
Next i

Пример неправильного кодирования № 2

Использование цикла foreach

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

oSPWeb = oSPSite.OpenWeb();

foreach(SPWeb oSPWeb2 in oSPWebe.Webs)
{
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
}
Dim oSPWeb, oSPWeb2 As SPWeb
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

oSPWeb = oSPSite.OpenWeb()

For Each oSPWeb2 As SPWeb In oSPWebe.Webs
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
Next

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

Рекомендуемый прием написания кода № 1

Использование цикла For

int i;

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

oSPWeb = oSPSite.OpenWeb();

for(i = 0;i < oSPWeb.Webs.Count;i++)
{
   oSPWeb2 = oSPWeb.Webs[i];
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
   oSPWeb2.Dispose();
}

oSPWeb.Dispose();
Dim i As Integer

Dim oSPWeb, oSPWeb2 As SPWeb
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

oSPWeb = oSPSite.OpenWeb()

For i = 0 To oSPWeb.Webs.Count - 1
   oSPWeb2 = oSPWeb.Webs(i)
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
   oSPWeb2.Dispose()
Next i

oSPWeb.Dispose()

Рекомендуемый прием написания кода № 2

Использование цикла foreach

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

oSPWeb = oSPSite.OpenWeb();

foreach(SPWeb oSPWeb2 in oSPWeb.Webs)
{
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
   oSPWeb2.Dispose();
}

oSPWeb.Dispose();
Dim oSPWeb, oSPWeb2 As SPWeb
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

oSPWeb = oSPSite.OpenWeb()

For Each oSPWeb2 As SPWeb In oSPWeb.Webs
   BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
   oSPWeb2.Dispose()
Next

oSPWeb.Dispose()

Рекомендуемый прием написания кода № 3

Использование цикла for с автоматическим удалением

int i;

SPWeb oSPWeb, oSPWeb2;
SPSite oSPSite = SPControl.GetContextSite(Context);

using(oSPWeb = oSPSite.OpenWeb())
{
   for(i = 0;i < oSPWeb.Webs.Count;i++)
   {
      Using(oSPWeb2 = oSPWeb.Webs[i])
      {
         BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title);
      }
   }
}
Dim i As Integer
Dim oSPSite As SPSite = SPControl.GetContextSite(Context)

Using oSPWeb As SPWeb = oSPSite.OpenWeb()
    For i = 0 To oSPWeb.Webs.Count - 1
        Using oSPWeb2 As SPWeb = oSPWeb.Webs(i)
                BuildTableRow(oDisplayTable, "Web", oSPWeb2.Title)
        End Using
    Next
End Using

Другие объекты, требующие удаления

В этом разделе описывается, когда требуется вызов метода Dispose для других объектов SharePoint.

Свойство Microsoft.SharePoint.Portal.SiteData.Area.Web

Свойство Web класса SharePoint.Portal.SiteData.Area возвращает новый объект SPWeb каждый раз при доступе к нему. Любое использование свойства Area.Web должно иметь соответствующий вызов метода Dispose. Хотя классы Area и AreaManager не являются устаревшими, они могут создавать проблемы при переносе старого кода.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_500.

Пример неправильного написания кода

void AreaWebLeak()
{
    // AreaManager and Area are obsolete in SharePoint Server, but this
    // should still be noted.
    Area area = AreaManager.GetArea(PortalContext.Current, new Guid("{GUID}"));
    string str = area.Web.Title;
    // SPWeb area.Web leak.
}
Private Sub AreaWebLeak()
    ' AreaManager and Area are obsolete in SharePoint Server, but this
    ' should still be noted.
    Dim area As Area = AreaManager.GetArea(PortalContext.Current, New Guid("{GUID}"))
    Dim str As String = area.Web.Title
    ' SPWeb area.Web leak.
End Sub

Рекомендуемый прием написания кода

public void AreaWebNoLeak()
{
    // AreaManager and Area are obsolete but this should still be noted.
    Area area = AreaManager.GetArea(PortalContext.Current, new Guid("{GUID}"));
    using (SPWeb areaWeb = area.Web)
    {
        string str = areaWeb.Title;
    }
}
Public Sub AreaWebNoLeak()
    ' AreaManager and Area are obsolete but this should still be noted.
    Dim area As Area = AreaManager.GetArea(PortalContext.Current, New Guid("{GUID}"))
    Using areaWeb As SPWeb = area.Web
        Dim str As String = areaWeb.Title
    End Using
End Sub

Методы SPControl.GetContextSite и SPControl.GetContextWeb

Если объект получен из объектов контекста (методы GetContextSite и GetContextWeb в классе SPControl), вызывающее приложение не должно вызывать метод Dispose для объекта. Такое действие может вызвать непредсказуемое поведение объектной модели SharePoint или ошибку. Причиной является внутренний список, сохраненный в объектах SPSite и SPWeb, получаемых таким образом. На внутреннем уровне объектная модель перечисляет этот список после завершения обработки страницы для правильного удаления объектов.

Следует также удалить объект, созданный из этих объектов, — например, если веб-сайт открыт из объекта SPSite, полученного с помощью метода GetContextSite.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_210.

Пример неправильного написания кода

void SPControlBADPractice()
{
    SPSite siteCollection = SPControl.GetContextSite(Context);
    siteCollection.Dispose();   // DO NOT DO THIS.
    SPWeb web = SPControl.GetContextWeb(Context);
    web.Dispose();  // DO NOT DO THIS.
}
Private Sub SPControlBADPractice()
    Dim siteCollection As SPSite = SPControl.GetContextSite(Context)
    siteCollection.Dispose() ' DO NOT DO THIS.
    Dim web As SPWeb = SPControl.GetContextWeb(Context)
    web.Dispose() ' DO NOT DO THIS.
End Sub

Рекомендуемый прием написания кода

void SPControlBestPractice()
{
    SPSite siteCollection = SPControl.GetContextSite(Context);
    SPWeb web = SPControl.GetContextWeb(Context);
    // Do NOT call Dispose().
}
Private Sub SPControlBestPractice()
      Dim siteCollection As SPSite = SPControl.GetContextSite(Context)
      Dim web As SPWeb = SPControl.GetContextWeb(Context)
      ' Do NOT call Dispose().
End Sub

Microsoft.SharePoint.WebPartPages.SPLimitedWebPartManager

Класс SPLimitedWebPartManager содержит ссылку на внутренний объект SPWeb, который должен быть удален.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_160.

Пример неправильного написания кода

void SPLimitedWebPartManagerLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPFile page = web.GetFile("Source_Folder_Name/Source_Page");
            SPLimitedWebPartManager webPartManager =
                page.GetLimitedWebPartManager(PersonalizationScope.Shared);
            // SPWeb object webPartManager.Web leaked.
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub SPLimitedWebPartManagerLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim page As SPFile = web.GetFile("Source_Folder_Name/Source_Page")
            Dim webPartManager As SPLimitedWebPartManager = page.GetLimitedWebPartManager(PersonalizationScope.Shared)
            ' SPWeb object webPartManager.Web leaked.
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода

void SPLimitedWebPartManagerLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPFile page = web.GetFile("Source_Folder_Name/Source_Page");
            SPLimitedWebPartManager webPartManager =
                page.GetLimitedWebPartManager(PersonalizationScope.Shared);
                webPartManaber.Web.Dispose();
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub SPLimitedWebPartManagerLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim page As SPFile = web.GetFile("Source_Folder_Name/Source_Page")
            Dim webPartManager As SPLimitedWebPartManager = page.GetLimitedWebPartManager(PersonalizationScope.Shared)
            webPartManaber.Web.Dispose()
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Microsoft.SharePoint.Publishing.PublishingWeb

ПримечаниеПримечание

Пространство имен Microsoft.SharePoint.Publishing принадлежит SharePoint Server 2010. Этот раздел применяется к SharePoint Server 2010 и не применяется к SharePoint Foundation 2010.

Метод GetPublishingWebs класса PublishingWeb возвращает объект PublishingWebCollection. Нужно вызвать метод Close для каждого перечисленного объекта innerPubWeb. При вызове только метода GetPublishingWeb не требуется вызывать Close.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_300.

Пример неправильного написания кода

void PublishingWebCollectionLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            // Passing in SPWeb object that you own, no dispose needed on
            // outerPubWeb.
            PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web);

            PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs();
            foreach (PublishingWeb innerPubWeb in pubWebCollection)
            {
                // innerPubWeb leak.
            }
            // PublishingWeb will leak for each innerPubWeb referenced
        } // SPWeb object web.Dispose() automatically called.
    } // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub PublishingWebCollectionLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            ' Passing in SPWeb object that you own, no dispose needed on
            ' outerPubWeb.
            Dim outerPubWeb As PublishingWeb = PublishingWeb.GetPublishingWeb(web)

            Dim pubWebCollection As PublishingWebCollection = outerPubWeb.GetPublishingWebs()
            For Each innerPubWeb As PublishingWeb In pubWebCollection
                ' innerPubWeb leak.
            Next
            ' PublishingWeb will leak for each innerPubWeb referenced
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода

void PublishingWebCollectionNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            // Passing in SPWeb object that you own, no dispose needed on
            // outerPubWeb.
            PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web);
            PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs();
            foreach (PublishingWeb innerPubWeb in pubWebCollection)
            {
                try
                {
                    // ...
                }
                finally
                {
                    if(innerPubWeb != null)
                        innerPubWeb.Close();
                }
            }
        }  // SPWeb object web.Dispose() automatically called.
    } // SPSite object siteCollection.Dispose() automatically called.
}
Private Sub PublishingWebCollectionNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            ' Passing in SPWeb object that you own, no dispose needed on
            ' outerPubWeb.
            Dim outerPubWeb As PublishingWeb = PublishingWeb.GetPublishingWeb(web)
            Dim pubWebCollection As PublishingWebCollection = outerPubWeb.GetPublishingWebs()
            For Each innerPubWeb As PublishingWeb In pubWebCollection
                Try
                    ' ...
                Finally
                    If innerPubWeb IsNot Nothing Then
                        innerPubWeb.Close()
                    End If
                End Try
            Next
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub
ПримечаниеПримечание

Существует сходное требование к вызову Close для каждого объекта PublishingWeb, созданного вызовом метода Add в коллекции PublishingWebCollection, возвращенной Microsoft.SharePoint.Publishing.PublishingWeb.GetPublishingWebs. Пример кода см. в разделе для метода GetPublishingWebs(). Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_310.

Метод Microsoft.SharePoint.Publishing.PublishingWeb.GetVariation возвращает объект PublishingWeb, который должен быть удален.

ПримечаниеПримечание

Эта рекомендация решает проблему, определяемую средством проверки удаления объектов SharePoint (Возможно, на английском языке) как SPDisposeCheckID_320.

Пример неправильного написания кода

void GetVariationLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);  // Passing in SPWeb object, so no Close() needed
            VariationLabel variationLabel = Variations.Current.UserAccessibleLabels[0];
            PublishingWeb variationPublishingWeb = publishingWeb.GetVariation(variationLabel);  // Must be Closed().
            // ...
        } // SPWeb object outerWeb.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub GetVariationLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim publishingWeb As PublishingWeb = PublishingWeb.GetPublishingWeb(web) ' Passing in SPWeb object, so no Close() needed
            Dim variationLabel As VariationLabel = Variations.Current.UserAccessibleLabels(0)
            Dim variationPublishingWeb As PublishingWeb = publishingWeb.GetVariation(variationLabel) ' Must be Closed().
            ' ...
        End Using ' SPWeb object outerWeb.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Рекомендуемый прием написания кода

void GetVariationNoLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            PublishingWeb variationPublishingWeb = null;
            try
            {
                PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);  // Passing in SPWeb object, so no Close() needed.
                VariationLabel variationLabel = Variations.Current.UserAccessibleLabels[0];
                variationPublishingWeb = publishingWeb.GetVariation(variationLabel);  // Must be Closed().
                // ...
            }
            finally
            {
                if(variationPublishingWeb != null)
                    variationPublishingWeb.Close();
            }
        } // SPWeb object web.Dispose() automatically called.
    }  // SPSite object siteCollection.Dispose() automatically called. 
}
Private Sub GetVariationNoLeak()
    Using siteCollection As New SPSite("http://moss")
        Using web As SPWeb = siteCollection.OpenWeb()
            Dim variationPublishingWeb As PublishingWeb = Nothing
            Try
                Dim publishingWeb As PublishingWeb = PublishingWeb.GetPublishingWeb(web) ' Passing in SPWeb object, so no Close() needed.
                Dim variationLabel As VariationLabel = Variations.Current.UserAccessibleLabels(0)
                variationPublishingWeb = publishingWeb.GetVariation(variationLabel) ' Must be Closed().
                ' ...
            Finally
                If variationPublishingWeb IsNot Nothing Then
                    variationPublishingWeb.Close()
                End If
            End Try
        End Using ' SPWeb object web.Dispose() automatically called.
    End Using ' SPSite object siteCollection.Dispose() automatically called.
End Sub

Способы удаления объектов в методах

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

public class CrossMethodLeak
{
    private SPSite _siteCollection = null;
    private SPWeb _web = null;

    public void MethodA()
    {
        _siteCollection = new SPSite("http://moss");
        _web = _siteCollection.OpenWeb();
    }

    public void MethodB()
    {
        if (_web != null)
        {
            string title = _web.Title;
        }
    }

    public void MethodC()
    {
        if (_web != null)
        {
            string name = _web.Name;
        }
    }
}
Public Class CrossMethodLeak
    Private _siteCollection As SPSite = Nothing
    Private _web As SPWeb = Nothing

    Public Sub MethodA()
        _siteCollection = New SPSite("http://moss")
        _web = _siteCollection.OpenWeb()
    End Sub

    Public Sub MethodB()
        If _web IsNot Nothing Then
            Dim title As String = _web.Title
        End If
    End Sub

    Public Sub MethodC()
        If _web IsNot Nothing Then
            Dim name As String = _web.Name
        End If
    End Sub
End Class

Заключение

Так как многие объекты SharePoint реализуют интерфейс IDisposable, следует с вниманием использовать такие объекты, чтобы избежать их сохранения в памяти. Следуя рекомендациям по удалению объектов SharePoint, описанным в этой статье, можно обеспечить надежность настраиваемого кода.

Благодарности

Мы хотим высказать благодарность следующим специалистам за их вклад и руководство при создании этой статьи:

  • Steve Sheppard, корпорация Майкрософт

  • Chris Gideon, корпорация Майкрософт

  • Rashid Aga, корпорация Майкрософт

См. также

Другие ресурсы

Средство проверки удаления объектов SharePoint (Возможно, на английском языке)

Руководство по SharePoint: шаблоны и примеры (Возможно, на английском языке)

Центр разработчика Windows SharePoint Services

Центр разработчика SharePoint Server 2007

Рекомендации по настройке продуктов и технологий SharePoint (Возможно, на английском языке)

Центр ресурсов и рекомендаций для SharePoint Server 2007 (Возможно, на английском языке)

Блог разработчика SharePoint (Roger Lamb) (Возможно, на английском языке)

Рекомендации: общие вопросы кодирования при использовании объектной модели SharePoint