C#入門編

C#入門編(18)NuGetパッケージの使い方 ~CSVファイルを読み込む~【Visual Studio+nuget】

実践的なアプリ開発では、他人が作った既存部品をいかにうまく使うかがキモになります。

だいたいのアプリ開発は以下のイメージです。

プロ太

ほぼ既存部品で、自分が作る部分はわずかなのです。

C#では、他の人が作ってくれた様々な既存部品(パッケージ)を簡単に使える仕組みとして「NuGet」というパッケージ管理システムがあります。

本記事では以下について解説します。

  • なぜ既存部品(パッケージ)を使うのが重要か?
  • Visual Studio上でNuGetを使う方法
  • 実践的な例(CSVファイル読み込み)
  • パッケージの選び方
プロ太

NuGetパッケージを使いこなせると、世の中にある便利な既存部品を使ってアプリ開発を効率よく進められるようになります。

一緒に学んでいきましょう!

演習のコード一式はGitHubにあります。

YouTubeの動画は以下にあります。

講義1:NuGetパッケージとは?

なぜ既存部品を使うのが重要か?

Web上にはすでに多くの人が作成した高品質な既存部品が公開されています。例えば以下のようなものがあります。

  • CSVやExcelなどのファイル入出力(例:CsvHelper、NPOI)
  • 数値計算、統計処理(例:Microsoft.ML、MathNet.Numerics)
  • PDFの生成(例:PDFsharp)
  • 画像処理(例:OpenCvSharp4)
  • ロギング(例:log4net、Serilog)

これらの機能を1から自分で実装すると、多大な労力がかかります

そのため、「車輪の再発明」(既にあるものを新しく作ること)を避け、既存部品を活用することが重要です。

現代のアプリ開発では、自分で書くコードよりも他の人が作ったコード(既存部品)の方が圧倒的に多いのが普通です。

既存部品は以下のような名称で呼ばれることが多いです。

  • ライブラリ:特定の機能をまとめた部品(例:数値計算、画像処理など)
  • フレームワーク:アプリ基盤となる大規模な部品(例:ASP.NET Core)
  • パッケージ:(C#においては)NuGetで配布される部品の一般的な呼び方

それぞれ定義は微妙に異なります。(プログラミング言語によってもその定義が異なる場合もあります)

プロ太

この記事では主に「パッケージ」という言葉を使って説明します。

NuGetとは?

NuGetは、Microsoftが提供する.NET用のパッケージ管理システムです。 以下のような機能を提供します。

  • パッケージの検索とインストール
  • パッケージのバージョン管理
  • 依存関係の自動管理

必要な機能を持つパッケージを簡単に探して導入できます。Visual Studio上で視覚的に操作可能です。(もちろん、コマンドラインからの操作も可能です)

最新バージョンへの更新が簡単にできます。 古いバージョンへの切り替えも可能です。

また、「パッケージAがパッケージBを必要とする場合、自動でBもインストール」といった依存関係の自動管理も行ってくれます。

プロ美

身近な例えでいうと、App Store, Google Play、Microsoft Storeみたいな存在ってことだね!

プロ太

そうですね。必要な機能(パッケージ)を検索して「インストール」するだけで、すぐに使える状態になります。

演習:Visual StudioでNuGetを使う ~CSVファイル読み込み機能~

これまでの演習で作ってきたレストランメニュープログラムを拡張し、メニュー情報をCSVファイルから読み込めるようにしましょう。

Visual StudioでNuGetを使って「CSVファイルを読み込む機能」を提供しているパッケージ(CsvHelper)を導入してみましょう。

レストランメニュー表生成プログラムで、CSVファイルの読み込み機能を入門編(14)で作りましたね。

C#入門編(14)例外処理の基本(try,catch,throw)~アプリの「想定外」を防ぐ~ C#入門編では、基本的な文法やオブジェクト指向、インターフェイス、ジェネリック型などについてこれまで学んできました。 今回はC#...

このプログラムではCSVファイルの読み込み部分を自分で作成していました。今回は、これを他の人が作った既存部品で置き換えましょう。

プロ太

わかりやすくするため、元のコードについては例外処理のコードなど簡易化しています。(また、最新の.NET9を使うようにしました)

元のコード

自前で実装した元のコード(CsvMenuReader.cs)は以下です。
(元のコードはこちらにあります。)

using Restaurant.Menus;

namespace Restaurant.Readers
{
    internal class CsvMenuReader
    {
        private readonly string _filePath;
        // CSV列の定数定義
        private const int ColumnType = 0;
        private const int ColumnName = 1;
        private const int ColumnDescription = 2;
        private const int ColumnPrice = 3;
        private const int ColumnIsVegetarian = 4;
        private const int ColumnIsCold = 5;

        public CsvMenuReader(string filePath)
        {
            _filePath = filePath;
        }

        public List<Menu> ReadMenus()
        {
            // CSVファイルの全行を読み込む
            string[] lines = File.ReadAllLines(_filePath);
            var menus = new List<Menu>();

            // ヘッダー行をスキップしてデータ行を処理する
            for (int lineIndex = 1; lineIndex < lines.Length; lineIndex++)
            {
                string[] parts = lines[lineIndex].Split(',');

                string type = parts[ColumnType].Trim();
                string name = parts[ColumnName].Trim();
                string description = parts[ColumnDescription].Trim();
                int price = int.Parse(parts[ColumnPrice].Trim());

                Menu menu;
                switch (type.ToLower())
                {
                    case "main":
                        bool isVegetarian = bool.Parse(parts[ColumnIsVegetarian].Trim());
                        menu = new MainMenu(name, description, price, isVegetarian);
                        break;
                    case "drink":
                        bool isCold = bool.Parse(parts[ColumnIsCold].Trim());
                        menu = new DrinkMenu(name, description, price, isCold);
                        break;
                    default:
                        throw new FormatException($"不明なメニュータイプです: {type} (行: {lineIndex})");
                }

                if (menu != null)
                {
                    menus.Add(menu);
                }
            }
            return menus;
        }
    }
}

CSVをテキストとして読み込み、1行ずつ区切り文字をなどチェックしながら各セルの内容について、適切な型へ変換させて読み込む…とすべて自前で書いています。

演習コード

以下の手順で、CSVHelperを使ったコードに置き換えます。

  • 手順1:CSVHelperパッケージを導入
  • 手順2:CsvMenuReader.csを修正

手順1:CSVHelperパッケージを導入

Visual Studioでソリューションを開きましょう。

「ソリューションエクスプローラ」で「Restaurant」プロジェクトを右クリックし、NuGetパッケージの管理を選びます。

次に、NuGetパッケージマネージャの画面で「参照」タブを選び、「Csv」で検索し、CsvHelperを選び、インストールを押しましょう。

プロ太

バージョンについては「最新の安定版」を選んでおけばよいでしょう。

以下の画面がでるので「適用」をクリックし、インストールを実行しましょう。

ライセンスを確認し「同意する」をクリックします。

Visual Studioで下側にある「出力ウィンドウ」で以下のように表示されればインストールは完了です!

プロ美

けっこう簡単だね!

プロ太

はい、NuGetを使うとパッケージのインストール自体は簡単ですね。パッケージのバージョン変更、アンインストールも簡単に行えます。

また、脆弱性のあるパッケージのバージョンもすぐにわかるようになっています。

手順2:CsvMenuReader.csを修正

CsvHelperを使って書き換えたコードは以下になります。

using CsvHelper;
using CsvHelper.Configuration.Attributes;
using System.Globalization;
using Restaurant.Menus;

namespace Restaurant.Readers
{
    //★(a)メニューデータのマッピング用クラス
    public class MenuRecord
    {
        [Name("Type")]     // CSVのヘッダー名を指定
        required public string Type { get; set; }
        
        [Name("Name")]
        required public string Name { get; set; }
        
        [Name("Description")]
        required public string Description { get; set; }
        
        [Name("Price")]
        required public int Price { get; set; }
        
        [Name("IsVegetarian")]
        required public bool IsVegetarian { get; set; }
        
        [Name("IsCold")]
        required public bool IsCold { get; set; }
    }

    internal class CsvMenuReader
    {
        private readonly string _filePath;

        public CsvMenuReader(string filePath)
        {
            _filePath = filePath;
        }

        public List<Menu> ReadMenus()
        {
            var menus = new List<Menu>();

            //★(b)CSVの読み込み
            using (var reader = new StreamReader(_filePath))
            using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
            {
                //★(c)CSVからオブジェクトへマッピング
                var records = csv.GetRecords<MenuRecord>().ToList();

                //★(d)マッピングしたオブジェクトを利用
                foreach (var record in records)
                {
                    Menu menu = record.Type.ToLower() switch
                    {
                        "main" => new MainMenu(
                            record.Name, 
                            record.Description, 
                            record.Price, 
                            record.IsVegetarian
                        ),
                        "drink" => new DrinkMenu(
                            record.Name, 
                            record.Description, 
                            record.Price, 
                            record.IsCold
                        ),
                        _ => throw new FormatException(
                            $"{record.Type}というメニュータイプは無効です。"
                        )
                    };
                    menus.Add(menu);
                }
            }

            return menus;
        }
    }
}

それぞれポイントを説明します。

(a)自動マッピング用のMenuRecordクラス

MenuRecrodはCSVの各行をC#のオブジェクトへ自動マッピングするためのクラスです。

[Name]属性でCSVのヘッダー名とプロパティをひも付けます。また、required修飾子は必須項目であることを示しています。

    //★(a)メニューデータのマッピング用クラス
    public class MenuRecord
    {
        [Name("Type")]     // CSVのヘッダー名を指定
        required public string Type { get; set; }
        
        [Name("Name")]
        required public string Name { get; set; }
        
        [Name("Description")]
        required public string Description { get; set; }
        
        // ...
    }

属性はクラスやプロパティに対して追加情報を設定する機能です。(参考

required修飾子はプロパティが必ず値を持つことを保証する機能で、そのプロパティに値が設定されていない場合はコンパイルエラーとなります。 (参考

(b),(c)CSVからの読み込みとオブジェクトへのマッピング

StreamReaderでファイルを開き CsvReaderでCSV形式として読み込み GetRecordsで指定したクラスのインスタンスへ自動マッピングします。

using (var reader = new StreamReader(_filePath))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    var records = csv.GetRecords<MenuRecord>().ToList();
    // ...

(d)マッピングしたオブジェクトを利用

マッピングされたオブジェクト(CSVの1行相当)から必要な情報を取り出し、メニューのインスタンスを作っています。

foreach (var record in records)
{
    Menu menu = record.Type.ToLower() switch
    {
        "main" => new MainMenu(
            record.Name, 
            record.Description, 
            record.Price,
            record.IsVegetarian
        ),
        // ...
    };
}

アプリ実行

例えば、以下のCSVファイルを入力してみましょう。

Type,Name,Description,Price,IsVegetarian,IsCold
Main,黒毛和牛ステーキ,ジューシーで柔らかなステーキです。,3000,false,false
Main,ベジタブルカレー,野菜をたっぷりと使った、スパイシーなカレーです。,2400,true,false
Drink,メロンソーダ,爽やかな甘さが楽しめるメロンソーダです。,400,false,true
Drink,ホットコーヒー,丁寧に焙煎されたコーヒー豆を使用しています。,500,false,false

以下の表になっています。

アプリを実行すると以下のように表示されるHTMLが出力されるはずです。

プロ美

NuGetでCSVHelperをインストールして使うことができたね!

プロ太

最初にCSVHelperの使い方を学ぶ必要はありますが、自分でゼロから作るよりも、高品質なものが素早く作れるでしょう。

講義2:NuGetパッケージの選び方

基本的な考え方

アプリ開発の成功は適切なパッケージ選びで決まると言っても過言ではありません。

信頼性の低いパッケージを選んでしまうと、バグや脆弱性、保守性の問題などプロジェクト全体に影響を及ぼす可能性があるためです。

パッケージを選ぶ際は、以下のポイントをチェックすると良いでしょう。

  • (1)利用実績・人気度
  • (2)メンテナンス状況
  • (3)ライセンス
  • (4)ドキュメントの充実度

(1)利用実績・人気度

利用実績・人気度としては以下のような指標があります。

  • NuGetギャラリーでのダウンロード数
  • GitHubでのStar数(ソースコードが公開されている場合)
  • Stack Overflowなどでの質問・回答の数

例えば、先ほど使用したCsvHelperは、累計2.8億件以上のダウンロード数があり、多くの開発者に使われている実績があります。

以下のように経年変化で傾向をみることもできます。

人気のあるパッケージは、バグも少なく、ドキュメントも充実している傾向にあります。

(2)メンテナンス状況

メンテナンス状況として以下のような指標があります。

  • 最終更新日
  • バージョンの更新頻度
  • Issue(不具合報告)への対応状況
  • Pull Request(修正提案)への対応状況

長期間更新がないパッケージは、重大な不具合が修正されない可能性があります。定期的にメンテナンスされているパッケージを選びましょう。

プロ太

パッケージ更新に必要な頻度は、成熟度やセキュリティパッチの必要性などよっても変わってきます。

まあ、例えば最後に更新されたのが数年以上前だった場合にはちょっと注意して見たほうがよいかもしれませんね。

(3)ライセンス

ライセンスには以下のような種類があります。サービス開発などを行う場合、商用利用が可能かどうかも重要な観点ですね。

  • MITライセンス:商用利用も含めて自由に使える
  • Apache License 2.0:特許関連の保護も含む
  • GPLv3:GPLv3ライセンスのパッケージを組み込んだアプリ全体もGPLv3ライセンスとなり、アプリ全体のソースコード公開が必要になる
プロ太

ライセンスについては、(特に仕事に使う場合など)原文を確認して使うようにするとよいでしょう。

なかにはGPL系ライセンスのように強い制約を持つものもあるため注意が必要です。

(4)ドキュメントの充実度

チュートリアル、サンプルコード、APIリファレンス、記事などのドキュメントが充実しているかどうかは重要です。

プロ美

演習で使ったCsvHelperは便利だけど、使い方はドキュメントを見る必要があるものね…。

プロ太

そのとおりですね。ドキュメントの充実度は「(1)利用実績・人気度」とも相関が強いです。

また、最近ではドキュメントの充実度は、ChatGPT等のAIへ質問をしたときの「回答の精度」にも関わってきます。これは、AIがドキュメントに基づき学習を行っているためです。

ドキュメントが充実していると、AI活用時にも有利というわけですね。AI活用については以下の記事も参考にしてください。

プログラミング初心者のためのチャット型AI活用ガイド【ChatGPT入門】 プログラミング学習において、ChatGPTなどのチャット型AIは非常に便利なツールです。 しかし、プログラミング初心者がAIを使...

実践的な選び方

(1)~(4)のポイントを中心に検討するわけですが、実際には例えば以下のような手順で選ぶとよいでしょう。

  1. 検索する:NuGetギャラリー、Web等で検索(例:CSV reader C#)
  2. 候補を絞り込む:ダウンロード数で上位のものを選び、ライセンスや更新状況をチェック
  3. 詳細を確認する:ドキュメントを読みサンプルコードを試す。
  4. 試験的に導入する:小規模なプロジェクトで試して問題なければ本番でも使用する。

このように段階的に確認することで、プロジェクトに適したパッケージを選ぶことができます。

プロ太

チャット型AIを活用して複数のパッケージを比較してもらうのもおすすめです!

Web検索連携ができるAI(例:ChatGPT、Perplexity)を使うと最新のWeb上の情報に基づいて比較検討させることも可能です。

まとめ

アプリ開発で使える便利な既存部品としてNuGetパッケージについて解説しました。

パッケージ管理システムNuGetを使えば、Visual Studioから簡単に既存部品を導入できます。

演習では、CSVファイル読み込み機能を自前実装からCSVHelperパッケージを使った実装へ置き換えました。

既存部品を活用することで、高品質なコードを効率よく実装できます。

NuGetパッケージの選び方として、(1)利用実績・人気度、(2)メンテナンス状況、(3)ライセンス、(4)ドキュメント充実度という4つの観点を紹介しました。

実践的なアプリ開発では、自分で書くコードよりも他人が作った既存部品のコードの方が圧倒的に多いのが普通です。

うまく既存のパッケージを使って、効率よく自分のアプリを作っていきましょう。

プロ太

引き続き、C#やプログラミングの考え方を一緒に学んでいきましょう!

ABOUT ME
プロ太
プログラミングを勉強している人へ情報を発信していきます! ・情報工学分野で博士(工学)の学位取得 ・言語:C# 、Java、C/C++、Python、JavaScript/TypeScript等 ・仕事は主に上流工程(WF開発・Agile開発、OSS開発経験あり) ・趣味で開発:3Dゲーム、Webアプリ、言語処理系等

ご依頼・ご相談について

プログラミング学習のご相談、お仕事のご依頼については、
こちらのお問い合わせページをご確認ください。