ROBOT PAYMENT TECH-BLOG

株式会社ROBOT PAYMENTのテックブログです

C#でのメモリ解放について

こんにちは。決済システムでエンジニアをやっております hoshino33 です。 2022年も残りわずかです。といってもこの記事が公開される頃にはきっと2023年になっているはずですね。 今回はC#でのメモリ解放について記載したいと思います。

はじめに

C#を触れている方は既知の内容かとおもいますが、これからC#を触る機会がある方の参考になれば幸いです。

メモリ解放について

C#では基本的にガベージコレクタでメモリ解放されるのであまり意識することはなそうですが、IDisposableを継承しているクラスに対してはメモリ解放を意識する必要があります。

StreamReaderを確認する

では、まずはStreamReaderを見てみましょう。 StreamReaderはTextReaderを継承しています。そして、TextReaderはIDisposableを継承しております。

public class StreamReader : TextReader
public abstract partial class TextReader : MarshalByRefObject, IDisposable

つまりIDisposableを継承しているのでStreamReaderはメモリ解放を意識する必要があります。

解放処理について(StreamReader)

ではどのように解放すればいいか見ていきます。 いくつか記載してみたいと思います。書き方違いますがどれも解放処理を記述しています。 ※例外処理は省略しております

try~finally句を利用

finallyで解放するパターンです。

StreamReader sr = new StreamReader("TestFile.txt");
try
{
    string line;
    while ((line = sr.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }
}
finally
{
    sr?.Dispose();
}

using statementを利用

finallyに比較したらだいぶすっきりします。

using (StreamReader sr = new StreamReader("TestFile.txt"))
{
    string line;
    while ((line = sr.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }
}

using declarationを利用

一見同じに見えますがネストがなくなっています。 あまりないとは思いますが、解放するスコープを気にするとかではなければ、基本的にはこちらの記述が一番すっきりします。

using StreamReader sr = new StreamReader("TestFile.txt");
string line;
while ((line = sr.ReadLine()) != null)
{
    Console.WriteLine(line);
}

StreamReaderの解放処理を逆コンパイルしてみる

逆コンパイルしてみるとfinally句でどれもDisposeしているのがわかります。

// try~finally句を利用
StreamReader sr = new StreamReader("TestFile.txt");  
try  
{  
    string line;  
    while ((line = ((TextReader)sr).ReadLine()) != null)  
    {  
        Console.WriteLine(line);  
    }  
}  
finally  
{  
    if (sr != null)  
    {  
        ((TextReader)sr).Dispose();  
    }
}

// using statementを利用
StreamReader sr = new StreamReader("TestFile.txt");  
try  
{  
    string line;  
    while ((line = ((TextReader)sr).ReadLine()) != null)  
    {  
        Console.WriteLine(line);  
    }  
}  
finally  
{  
    ((System.IDisposable)sr)?.Dispose();  
}

// using declarationを利用
StreamReader sr = new StreamReader("TestFile.txt");  
try  
{  
    string line;  
    while ((line = ((TextReader)sr).ReadLine()) != null)  
    {  
        Console.WriteLine(line);  
    }  
}  
finally  
{  
    ((System.IDisposable)sr)?.Dispose();  
}

HttpClientを確認する

つぎは、HttpClientを見てみましょう。 こちらもIDisposableを継承しています。

public partial class HttpClient : HttpMessageInvoker
public class HttpMessageInvoker : IDisposable

一見同じように解放すれば良さそうですがそうではありません。

// 利用毎にインスタン化して解放してはいけない
using HttpClient httpClient = new HttpClient();
HttpResponseMessage response = await client.GetAsync("http://www.contoso.com/");

HttpClientについて

HttpClient は、1 回インスタンス化され、アプリケーションの有効期間中に再利用されることを目的としています。 .NET Core と .NET 5 以降では、HttpClient はハンドラー インスタンス内の接続をプールし、複数の要求間で接続を再利用します。 すべての要求に対して HttpClient クラスをインスタンス化すると、大量の負荷で使用可能なソケットの数が使い果たされます。 この枯渇により、エラーが SocketException 発生します。

とあるように利用毎にインスタンス化し都度解放するのではなく、再利用するようにする必要があります。

public class TestClass
{
    private static readonly HttpClient httpClient = new HttpClient();

    public async Task TestMethod()
    {
        HttpResponseMessage response = await httpClient.GetAsync("http://www.contoso.com/");
    }
}

まとめ

IDisposableを継承しているクラスに対して解放を意識する必要がある。 ただし、HttpClientのように都度インスタンス化して解放してはいけない場合もある。 知識を深めてコーディングをエンジョイしていきましょう!



We are hiring!!

ROBOT PAYMENTでは一緒に働く仲間を募集しています!!!

speakerdeck.com
www.robotpayment.co.jp
🎉twitter採用担当アカウント開設!🎉どんどん情報発信していきます!!