名前空間
名前空間宣言と完全修飾子
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 { }
}
|
C#プログラムは名前空間を使ってクラスの分類・階層を整理している。.NET Framework自身が多数のクラスを編成するために名前空間を利用しており、自分で開発する際も名前空間を利用してクラスを整理できる。
名前空間は階層的に定義でき、名前空間を宣言する際はnamespace
キーワードを使う。階層的な名前空間は、複数のnamespace
を入れ子にして(例:リスト5-1のN1
とN2
)、もしくは名前空間を.
でつなげて(例:リスト5-1のN1.N2
)宣言できる。
別の名前空間内に定義されたクラスを参照する場合、名前空間とクラス名を.
でつなげた完全修飾子名で指定する。もしくは次の項で紹介するusing
ディレクティブを利用する。
なお、名前空間の命名規則であるが、Microsoftのガイドラインでは企業名.製品名.機能名
といった安定した名前を利用し、組織構造に変更があったときに名前空間に影響があるような名前付けは避けるべきとしている。
usingディレクティブ
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 { }
}
|
using
ディレクティブを利用すると、完全修飾子を使わずにクラス名のみで別の名前空間に属するクラスを参照できるようになる。
using alias
ディレクティブを使うと、名前空間またはクラス名のエイリアス(別名)を定義できる。例えばリスト5-2はMyDateTime
をクラス名「DateTime」のエイリアスとして利用しているが、このDateTime
のように.NET Frameworkの基本クラスライブラリに含まれるクラスと同じ名前のクラスが定義されている別のライブラリを利用する場合、通常であれば、DateTime
への参照が出現する箇所全てに完全修飾子を使って、どちらのDateTime
なのかを明確に記述する必要がある。エイリアスを使えば、その別名を使って明確に参照できるので、完全修飾子にする必要はなくなる(※前項にも出したガイドラインによれば、.NET Frameworkのクラス名と同じ名前は付けるべきではないが、参照するライブラリがそのガイドラインにのっとっていない場合があるため。また、複数のクラスライブラリを使うと、偶然、名前が同じクラスが定義されている場合もあるが、そんな場合にusing alias
が便利だ)。
クラス
クラスの宣言
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;
}
}
}
}
|
クラスは、フィールド、メソッド、コンストラクター、プロパティ、インデクサーなどを格納するデータ構造である。C#ではトップレベルのクラスはソースコードファイル(=.csファイル)の中に複数定義できる。
静的クラス
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;
}
}
|
静的(static
)クラスは、インスタンス化できないクラスで、静的メンバーのみを含めることができる。後述する拡張メソッドを定義するため利用されることも多い。
部分クラス
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");
}
}
|
部分(partial
)クラスは、1つのクラス定義を複数個所に分けて記述できる仕組みである。例えば、クラスの定義の一部を自動生成する場合などに、自動生成部分をpartial
で分離することにより、自動生成を再実行しやすくするといった使い方をする。
ジェネリクスの型パラメーター
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
{
}
|
型パラメーターを利用することで、クラスを定義する際にプレースホルダー(上記コードではT
/K
/V
)として型を提供し、そのクラスを利用する際に実際の型(上記コードではstring
やDateTime
など)を指定できる。ジェネリック(=汎用的)に使えるクラス/インターフェース/メソッドなどを定義できるので、ジェネリクス(Generics)と呼ばれ、基本クラスライブラリでもList<T>
クラスなど主にコレクションで多用されている。
型パラメーターはwhere
キーワードで制約を課すことができる。class
もしくはstruct
で「参照型」もしくは「構造体」である制約を課したり、「指定したインターフェースを実装したクラス」や「指定したクラスを継承したクラス」という制約を課したりできる。また、new ()
制約により「publicな引数なしのコンストラクターを持つ」という制約を課すことで、型パラメーターに指定したクラスを(例えばnew T()
という)コンストラクター呼び出しでインスタンス化することもできる。
アクセス修飾子と入れ子クラス
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
{ }
}
|
トップレベルのクラスはpublic
もしくはinternal
にできる。省略した場合のデフォルトはinternal
になる。internal
は同じアセンブリ内からのみアクセス可能だ。
また、クラスの内部に入れ子になったクラスを定義でき、この場合、private
/protected
/internal
/protected internal
/public
にできる。private
はそのクラス自身からのみ、protected
はそのクラス自身および派生したクラスからのみアクセス可能であり、protected internal
はprotected
もしくはinternal
なアクセスが可能である。省略した場合のデフォルトはクラスのフィールドやプロパティ同様、private
である。
入れ子になった型とそれを包含する型は、インスタンス同士に特別な関係はない。つまり包含する型のインスタンスと無関係に入れ子になった型をインスタンス化できる。入れ子になった型は、包含する型のprivate
やprotected
なメンバーにアクセス可能である。
定数とstatic readonlyフィールド
// 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);
}
}
}
|
※このサンプルに対応する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のソースコードをコンパイルし直す場合は問題なくX
Y
両方の値が2と表示される。しかし、ConsoleApp.exeをコンパイルし直すことなく、Library.dllのみを新しいものに更新した場合、定数の値は1と表示されたままである。これは、定数の値はConsoelApp.exeのコンパイル時のLibrary.dllの値を参照したままになるためだ。
このような性質があるため、プログラムを更新する際に変更する可能性がある値は定数として宣言するのを避けた方がいいだろう。
- 前のページへ
- 次のページへ
- 【記事のページ一覧】
- 1 プログラムの実行と制御/型と変数
- 2 演算子/ステートメント
- 3 名前空間/クラス
- 4 メソッド/プロパティ/イベント/インデクサー/演算子オーバーロード/コンストラクターコンストラクタとデストラクターデストラクタ
- 5 構造体/継承とインターフェース/列挙型/イテレーター/例外処理/リソースの解放
- 6 ラムダ式/非同期処理(async/await)