リファレンス、ドキュメント、ラーニング

マイクロソフト製ということもあり、必要な情報は同社がきっちりと揃えてくれています。

リファレンス、ドキュメント

リファレンス、ドキュメントはこちらのページにまとまっています。

ラーニング:Microsoft Learn

Microsoft が提供するさまざまなコンテンツで学習できます。

次の検索ページにて「.NET」で絞り込むと、C# 関連の学習コンテンツが見つかります。

https://docs.microsoft.com/ja-jp/learn/browse/

ラーニング:Microsoft Shows

こちらは動画コンテンツです。

こちらが検索ページです。「C#」で探すといくつか見つかります。

https://docs.microsoft.com/ja-jp/shows/browse

プレイグラウンド:Try .NET

.NET のプレイグラウンド環境として「Try .NET」が提供されています。

ブラウザーでさっと試すにはこのサイトが便利です。

https://try.dot.net/

チュートリアル例

さて、C# および .NET アプリケーションに関するチュートリアルとして、最も初級者向けのものをいくつかピックアップして取り組んでみました。

  1. C# の最初のステップ - Learn | Microsoft Docs
  2. C# 101 | Microsoft Docs
  3. C# を使用して .NET アプリケーションをビルドする - Learn | Microsoft Docs

1. C# の最初のステップ

C# という言語はもとより、「プログラミング言語とは」「コンパイルとは」というところから説明があります。

コンパイルとは

コンパイラと呼ばれる特殊なプログラムにより、ソース コードがコンピューターの CPU で実行可能な別の形式に変換されます。 前のユニットで緑の [実行] ボタンを使用したときに、あなたが記述したコードは最初にコンパイルされてから実行されました。

コードをコンパイルする必要があるのはなぜでしょうか。 ほとんどのプログラミング言語は、最初は暗号のように見えますが、数千から数百万の小さなスイッチをオンまたはオフにすることによって表現される、コンピューターの優先言語よりも簡単に理解できます。 コンパイラは、人間が解読可能な命令をコンピューターが理解可能な一連の命令に変換することで、この 2 つの世界を橋渡しします。

初めてプログラミングに触れる、という人にとっては最適なチュートリアルでしょう。

そしてチュートリアルの最後のテーマが「読みやすいコードを作成する」というものです。

以下は講座内で説明されていた、コメントの書き方の改善例です。

不適切なコメント

Random random = new Random();
string[] orderIDs = new string[5];
// Loop through each blank orderID
for (int i = 0; i < orderIDs.Length; i++)
{
    // Get a random value that equates to ASCII letters A through E
    int prefixValue = random.Next(65, 70);
    // Convert the random value into a char, then a string
    string prefix = Convert.ToChar(prefixValue).ToString();
    // Create a random number, padd with zeroes
    string suffix = random.Next(1, 1000).ToString("000");
    // Combine the prefix and suffix together, then assign to current OrderID
    orderIDs[i] = prefix + suffix;
}
// Print out each orderID
foreach (var orderID in orderIDs)
{
    Console.WriteLine(orderID);
}

このコードには 2 つの問題があります。

  • コードのコメントで、個々のコード行の明らかな機能について必要のないことが説明されています。 このようなコメントは、C# または .NET クラス ライブラリのメソッドの動作を説明するだけなので、低品質のコメントと見なされます。 読む人がこれらのアイデアをよく知らない場合は、docs.microsoft.com や IntelliSense を使用して検索できます。
  • コードのコメントで、コードによって解決されている問題のコンテキストが提供されていません。 このようなコメントは、読む人がこのコードの目的についての分析情報を得られないため (特に大規模なシステムに関連している場合)、低品質のコメントと見なされます。

改善されたコメント

このコードを改善するため、まず既存のコメントを削除してみましょう。

このコードをさらに改善するため、コードの上位レベルの目的を説明するコメントを追加します。

/*
  The following code creates five random OrderIDs
  to test the fraud detection process.  OrderIDs
  consist of a letter from A to E, and a three
  digit number. Ex. A123.
*/
Random random = new Random();
string[] orderIDs = new string[5];

for (int i = 0; i < orderIDs.Length; i++)
{
    int prefixValue = random.Next(65, 70);
    string prefix = Convert.ToChar(prefixValue).ToString();
    string suffix = random.Next(1, 1000).ToString("000");

    orderIDs[i] = prefix + suffix;
}

foreach (var orderID in orderIDs)
{
    Console.WriteLine(orderID);
}

2. C# 101

この動画は、以下2つのチュートリアルについて解説しながら進めていく形式になっていました。

前半で C# の基本的な文法を学び、後半はクラスを使ったオブジェクト指向なコードの書き方の学習に移ります。

具体例として、完成したコードを一部掲載しておきます。

BankAccount.cs

namespace Classes;

public class BankAccount
{
  private static int accountNumberSeed = 1234567890;

  public string Number { get; }

  public string Owner { get; set; }

  public decimal Balance
  {
    get
    {
      decimal balance = 0;
      foreach (var item in allTransactions)
      {
        balance += item.Amount;
      }

      return balance;
    }
  }

  private List<Transaction> allTransactions = new List<Transaction>();

  public BankAccount(string name, decimal initialBalance)
  {
    Number = accountNumberSeed.ToString();
    accountNumberSeed++;

    Owner = name;
    MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
  }

  public void MakeDeposit(decimal amount, DateTime date, string note)
  {
    if (amount <= 0)
    {
      throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
    }
    var deposit = new Transaction(amount, date, note);
    allTransactions.Add(deposit);
  }

  public void MakeWithdrawal(decimal amount, DateTime date, string note)
  {
    if (amount <= 0)
    {
      throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
    }
    if (Balance - amount < 0)
    {
      throw new InvalidOperationException("Not sufficient funds for this withdrawal");
    }
    var withdrawal = new Transaction(-amount, date, note);
    allTransactions.Add(withdrawal);
  }

  public string GetAccountHistory()
  {
    var report = new System.Text.StringBuilder();

    decimal balance = 0;
    report.AppendLine("Date\t\tAmount\tBalance\tNote");
    foreach (var item in allTransactions)
    {
      balance += item.Amount;
      report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
    }

    return report.ToString();
  }
}

3. C# を使用して .NET アプリケーションをビルドする

このチュートリアルでは、最初は C# の基本的な文法を、次に .NET の概要を学び、その後は次第に実践的な内容に入っていきます。

新しい .NET プロジェクトを作成してパッケージを処理する

まずはパッケージ(依存関係)の管理方法です。

https://docs.microsoft.com/ja-jp/learn/modules/dotnet-dependencies/

NuGet レジストリの依存関係を使用して、.NET アプリケーションをより迅速に開発します。 自分のプロジェクトで依存関係を管理する方法について説明します。

具体的には以下のコマンドなどが紹介されています。自分で調べればわかることではありますが、チュートリアルとしてこういうことを教えてくれるのはありがたいですね。

dotnet add package <name of dependency>

dotnet list package

dotnet list package --include-transitive

dotnet list package --outdated

dotnet remove package <name of dependency>

Visual Studio Code デバッガーを使用して .NET アプリを対話形式でデバッグする

そしてデバッガーの使用方法を学びます。

https://docs.microsoft.com/ja-jp/learn/modules/dotnet-debug/

Visual Studio Code を使用して .NET アプリを効率的にデバッグし、バグを迅速に修正する方法について学習します。

細部に渡るまで非常に丁寧に説明してくれています。

.NET アプリでファイルとディレクトリを操作する

それからローカルのファイルやディレクトリを操作する方法を学びます。

このような操作が必要なプログラムを作る場合には非常に参考になると思います。

https://docs.microsoft.com/ja-jp/learn/modules/dotnet-files/

C# と .NET を使用してファイルとディレクトリを操作するアプリを作成します。

ASP.NET Core コントローラーを使用して Web API を作成する

最後は Web API の作成方法を学びます。

https://docs.microsoft.com/ja-jp/learn/modules/build-web-api-aspnet-core/

作成、読み取り、更新、削除 (CRUD) 操作をサポートする ASP.NET Core コントローラーを使用して、RESTful サービスを作成します。

ピザのデータの CRUD 処理を行う Web API を作成します。完成したコードを掲載しておきます。

Controllers/PizzaController.cs

using ContosoPizza.Models;
using ContosoPizza.Services;
using Microsoft.AspNetCore.Mvc;

namespace ContosoPizza.Controllers;

[ApiController]
[Route("[controller]")]
public class PizzaController : ControllerBase
{
    public PizzaController()
    {
    }

    // GET all action
    [HttpGet]
    public ActionResult<List<Pizza>> GetAll() => PizzaService.GetAll();

    // GET by Id action
    [HttpGet("{id}")]
    public ActionResult<Pizza> Get(int id)
    {
        var pizza = PizzaService.Get(id);

        if (pizza == null)
            return NotFound();

        return pizza;
    }

    // POST action
    [HttpPost]
    public IActionResult Create(Pizza pizza)
    {
        PizzaService.Add(pizza);
        return CreatedAtAction(nameof(Create), new { id = pizza.Id }, pizza);
    }

    // PUT action
    [HttpPut("{id}")]
    public IActionResult Update(int id, Pizza pizza)
    {
        if (id != pizza.Id)
            return BadRequest();

        var existingPizza = PizzaService.Get(id);
        if (existingPizza is null)
            return NotFound();

        PizzaService.Update(pizza);

        return NoContent();
    }

    // DELETE action
    [HttpDelete("{id}")]
    public IActionResult Delete(int id)
    {
        var pizza = PizzaService.Get(id);

        if (pizza is null)
            return NotFound();

        PizzaService.Delete(id);

        return NoContent();
    }
}

Services/PizzaService.cs

using ContosoPizza.Models;

namespace ContosoPizza.Services;

public static class PizzaService
{
    static List<Pizza> Pizzas { get; }
    static int nextId = 3;
    static PizzaService()
    {
        Pizzas = new List<Pizza>
        {
            new Pizza { Id = 1, Name = "Classic Italian", IsGlutenFree = false },
            new Pizza { Id = 2, Name = "Veggie", IsGlutenFree = true }
        };
    }

    public static List<Pizza> GetAll() => Pizzas;

    public static Pizza? Get(int id) => Pizzas.FirstOrDefault(p => p.Id == id);

    public static void Add(Pizza pizza)
    {
        pizza.Id = nextId++;
        Pizzas.Add(pizza);
    }

    public static void Delete(int id)
    {
        var pizza = Get(id);
        if (pizza is null)
            return;

        Pizzas.Remove(pizza);
    }

    public static void Update(Pizza pizza)
    {
        var index = Pizzas.FindIndex(p => p.Id == pizza.Id);
        if (index == -1)
            return;

        Pizzas[index] = pizza;
    }
}

Models/Pizza.cs

namespace ContosoPizza.Models;

public class Pizza
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsGlutenFree { get; set; }
}