Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 

C# 早わかりリファレンス

― 3/6ページ: 名前空間/クラス ―

2024年4月18日

名前空間

名前空間宣言完全修飾子

C#
class Program
{
  static void Main(string[] args)
  {
    var a = new N1.A();
    var b = new N1.N2.B();
    var c = new N1.N2.C();
  }
}

namespace N1
{
  class A { }

  namespace N2
  {
    class C { }
  }
}

namespace N1.N2
{
  class B { }
}
リスト5-1 名前空間宣言と完全修飾子

 C#プログラムは名前空間を使ってクラスの分類・階層を整理している。.NET Framework自身が多数のクラスを編成するために名前空間を利用しており、自分で開発する際も名前空間を利用してクラスを整理できる。

 名前空間は階層的に定義でき、名前空間を宣言する際はnamespaceキーワードを使う。階層的な名前空間は、複数のnamespaceを入れ子にして(例:リスト5-1のN1N2)、もしくは名前空間を.でつなげて(例:リスト5-1のN1.N2)宣言できる。

 別の名前空間内に定義されたクラスを参照する場合、名前空間とクラス名を.でつなげた完全修飾子名で指定する。もしくは次の項で紹介するusingディレクティブを利用する。

 なお、名前空間の命名規則であるが、Microsoftのガイドラインでは企業名.製品名.機能名といった安定した名前を利用し、組織構造に変更があったときに名前空間に影響があるような名前付けは避けるべきとしている。

usingディレクティブ

C#
using System;
using System.Text;

namespace CSharpCheatSheet
{
  using MyDateTime = N2.DateTime;

  class Program
  {
    static void Main(string[] args)
    {
      // usingによりSystem.Text.StringBuilderを参照
      var sb = new StringBuilder();
      // System.DateTimeを参照
      var now = DateTime.Now;
      // namespace aliasによりCSharpCheatSheet.N2.MyDateTimeを参照
      var dateTime = new MyDateTime();
      // namespace aliasがない場合はこのように全ての参照箇所で完全修飾子が必要になる
      var dateTime2 = new N2.DateTime();
    }
  }
}

namespace N2
{
  public class DateTime { }
}
リスト5-2 usingディレクティブとusing aliasディレクティブ

 usingディレクティブを利用すると、完全修飾子を使わずにクラス名のみで別の名前空間に属するクラスを参照できるようになる。

 using aliasディレクティブを使うと、名前空間またはクラス名のエイリアス(別名)を定義できる。例えばリスト5-2はMyDateTimeをクラス名「DateTime」のエイリアスとして利用しているが、このDateTimeのように.NET Frameworkの基本クラスライブラリに含まれるクラスと同じ名前のクラスが定義されている別のライブラリを利用する場合、通常であれば、DateTimeへの参照が出現する箇所全てに完全修飾子を使って、どちらのDateTimeなのかを明確に記述する必要がある。エイリアスを使えば、その別名を使って明確に参照できるので、完全修飾子にする必要はなくなる(前項にも出したガイドラインによれば、.NET Frameworkのクラス名と同じ名前は付けるべきではないが、参照するライブラリがそのガイドラインにのっとっていない場合があるため。また、複数のクラスライブラリを使うと、偶然、名前が同じクラスが定義されている場合もあるが、そんな場合にusing aliasが便利だ)。

クラス

クラスの宣言

C#
using System;

namespace Library
{
  public class MyClass
  {
    // フィールド
    public static readonly int DefaultLimit = 10;

    private int term;

    // コンストラクター
    public MyClass() : this(DefaultLimit)
    { }

    public MyClass(int limit)
    {
      Limit = limit;
    }

    // プロパティ
    public int Limit { get; }

    // メソッド
    public void Try()
    {
      term = 0;
      while (term < Limit)
      {
        Console.WriteLine($"{term}回目");
        ++term;
      }
    }
  }
}
リスト6-1 クラスの宣言

 クラスは、フィールド、メソッド、コンストラクター、プロパティ、インデクサーなどを格納するデータ構造である。C#ではトップレベルのクラスはソースコードファイル(=.csファイル)の中に複数定義できる。

静的クラス

C#
class Program
{
  static void Main(string[] args)
  {
    var mile = MileConverter.MileToKm(1);
  }
}

// 静的クラス
public static class MileConverter
{
  // 静的メンバー(フィールド)
  public static readonly double MilePerKm = 0.62137;

  // 静的メンバー(メソッド)
  public static double KmToMile(double km)
  {
    return km * MilePerKm;
  }

  public static double MileToKm(double mile)
  {
    return mile / MilePerKm;
  }
}
リスト6-2 静的クラス

 静的(static)クラスは、インスタンス化できないクラスで、静的メンバーのみを含めることができる。後述する拡張メソッドを定義するため利用されることも多い。

部分クラス

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var my = new MyClass();
    my.Test();
    my.Test2();
  }
}

// 部分クラス(1つ目)
public partial class MyClass
{
  public void Test()
  {
    Console.WriteLine("Test");
  }
}

// 部分クラス(2つ目)
public partial class MyClass
{
  public void Test2()
  {
    Console.WriteLine("Test2");
  }
}
リスト6-3 部分クラス

 部分(partial)クラスは、1つのクラス定義を複数個所に分けて記述できる仕組みである。例えば、クラスの定義の一部を自動生成する場合などに、自動生成部分をpartialで分離することにより、自動生成を再実行しやすくするといった使い方をする。

ジェネリクスの型パラメーター

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    var c1 = new MyClass<string>();
    c1.Value = "a";
    c1.DefaultValue();

    var c2 = new MyClass<DateTime>();
    c2.Value = DateTime.Now;
    c2.DefaultValue();

    var c3 = new MyClass2<object>();
    c3.DefaultValue();

    var c4 = new MyClass3<Printable>();
    c4.Execute();

    var c5 = new MyClass4<string, DateTime>();
  }
}

// ジェネリック(=汎用的な)クラス。Tが型パラメーター
public class MyClass<T>
{
  public T Value { get; set; }

  public T DefaultValue()
  {
    return default(T);
  }
}

public class MyClass2<T> where T : new()
{
  public T DefaultValue()
  {
    // new () 制約があるため、Tのデフォルトコンストラクター呼び出しによるインスタンス化が可能
    return new T();
  }
}

public class MyClass3<T> where T : IPrintable, new()
{
  public void Execute()
  {
    // TはIPrintableインターフェースを実装している
    new T().Print();
  }
}

public interface IPrintable
{
  void Print();
}

public class Printable : IPrintable
{
  public void Print()
  {
    Console.WriteLine("Printable Prints");
  }
}

// 複数の型パラメーターがある場合は、それぞれに制約を指定可能
public class MyClass4<K, V>
  where K : class
  where V : struct
{

}
リスト6-4 型パラメーター

 型パラメーターを利用することで、クラスを定義する際にプレースホルダー(上記コードではTKV)として型を提供し、そのクラスを利用する際に実際の型(上記コードではstringDateTimeなど)を指定できる。ジェネリック(=汎用的)に使えるクラス/インターフェース/メソッドなどを定義できるので、ジェネリクス(Generics)と呼ばれ、基本クラスライブラリでもList<T>クラスなど主にコレクションで多用されている。

 型パラメーターはwhereキーワードで制約を課すことができる。classもしくはstructで「参照型」もしくは「構造体」である制約を課したり、「指定したインターフェースを実装したクラス」や「指定したクラスを継承したクラス」という制約を課したりできる。また、new ()制約により「publicな引数なしのコンストラクターを持つ」という制約を課すことで、型パラメーターに指定したクラスを(例えばnew T()という)コンストラクター呼び出しでインスタンス化することもできる。

アクセス修飾子入れ子クラス

C#
using System;

class Program
{
  static void Main(string[] args)
  {
    // protected internalなOuter1.Inner3クラスにはアクセスできるが、
    // privateなOuter1.Inner2クラスにはアクセスできない
    var inner = new Outer1.Inner3();
    inner.Execute();

    var outer = new Outer1();
    outer.Execute();
  }
}

public class Outer1
{
  private static void Run()
  {
    Console.WriteLine("Run");
  }

  public void Execute()
  {
    // 包含する型からなのでprivateなInner2にアクセスできる
    var inner = new Inner2();
    inner.Execute();
  }

  public class Inner1
  {
    public void Execute()
    {
      // 入れ子になったクラスから包含する型のprivateメソッドにアクセスできる
      Run();
    }
  }

  private class Inner2 : Inner1
  { }

  protected internal class Inner3 : Inner1
  { }
}
リスト6-5 入れ子になったクラスとアクセス修飾子

 トップレベルのクラスはpublicもしくはinternalにできる。省略した場合のデフォルトはinternalになる。internalは同じアセンブリ内からのみアクセス可能だ。

 また、クラスの内部に入れ子になったクラスを定義でき、この場合、privateprotectedinternalprotected internalpublicにできる。privateはそのクラス自身からのみ、protectedはそのクラス自身および派生したクラスからのみアクセス可能であり、protected internalprotectedもしくはinternalなアクセスが可能である。省略した場合のデフォルトはクラスのフィールドやプロパティ同様、privateである。

 入れ子になった型とそれを包含する型は、インスタンス同士に特別な関係はない。つまり包含する型のインスタンスと無関係に入れ子になった型をインスタンス化できる。入れ子になった型は、包含する型のprivateprotectedなメンバーにアクセス可能である。

定数static readonlyフィールド

C#
// Library.dll
using System;

namespace Library
{
  public class Utility
  {
    public const int X = 1;              // 定数
    public static readonly int Y = 1;    // 静的な読み取り専用
    // コンパイル時に値が決まらない場合はstatic readonlyフィールドが利用できる
    public static readonly DateTime DefaultDate = new DateTime(2016, 4, 1);
  }
}

// ConsoleApp.exe
using System;
using Library;

namespace CSharpCheatSheet
{

  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine(Utility.X);
      Console.WriteLine(Utility.Y);
    }
  }
}
リスト6-6 定数とstatic readonlyフィールド

このサンプルに対応するLINQPad用サンプルは存在しない。試すには、コンソールアプリ(ConsoleApp)とクラスライブラリ(Library)のプロジェクトを作成し、アプリ側からライブラリを参照してほしい。

 定数は、コンパイル時に値が決定するものをクラスメンバーとして宣言できる仕組みである。定数は静的にアクセスできるが、static修飾子は不要でstatic constと記述するとコンパイルエラーになる。

 定数とよく似た働きをするものとして、static readonlyなフィールドがある。コンパイル時に決定できない値、例えば引数を指定したDateTimeクラスのインスタンスなどはstatic readonlyなフィールドを利用できる。

 定数の利用に注意しないといけない場合として、アセンブリをまたがったときに参照されている側のアセンブリで定数を更新した場合の扱いがある。サンプルコードのように、Library.dll側に定数とstatic readonlyなフィールドを定義し、ConsoleApp.exeはLibrary.dllを参照しているものとしよう。このとき、Library.dll側の定数(X)とstatic readonlyなフィールドの値(Y)をともに1から2に変更した新しいバージョンのLibrary.dllに更新する状況を考える。コンパイルし直した新しいLibrary.dllを参照しているConsoleApp.exeのソースコードをコンパイルし直す場合は問題なくXY両方の値が2と表示される。しかし、ConsoleApp.exeをコンパイルし直すことなく、Library.dllのみを新しいものに更新した場合、定数の値は1と表示されたままである。これは、定数の値はConsoelApp.exeのコンパイル時のLibrary.dllの値を参照したままになるためだ。

 このような性質があるため、プログラムを更新する際に変更する可能性がある値は定数として宣言するのを避けた方がいいだろう。

サイトからのお知らせ

Twitterでつぶやこう!