C#でオブジェクトを使い始めるときに必ず通るのが、new
でのインスタンス化です。
本記事では、new
の基本から、配列や引数付きの生成パターン、そして初心者がつまずきやすいコンパイルエラーと対処法までを段階的に解説します。
最後に日々の実装で役立つチェックポイントもまとめます。
C#のnewによるインスタンス化の基本
インスタンスとクラスの関係
クラスは設計図、インスタンスはその設計図から作られる実体です。
C#ではclass
で設計図を定義し、new
で実体を作ります。
プロパティを通じてデータを保持し、メソッドで振る舞いを与えます。
以下は、Person
クラスを定義してインスタンス化する最小の例です。
using System;
public class Person
{
// 自動実装プロパティ(ゲッターとセッター)
public string Name { get; set; }
public int Age { get; set; }
// 引数なしコンストラクタ(初期値を設定)
public Person()
{
Name = "unknown";
Age = 0;
}
// インスタンスメソッド(自己紹介)
public void Greet()
{
Console.WriteLine($"こんにちは、{Name}です。{Age}歳です。");
}
}
public class Program
{
public static void Main()
{
// newでPersonのインスタンスを作成
Person p = new Person();
// プロパティを設定
p.Name = "Taro";
p.Age = 20;
// メソッドを呼び出し
p.Greet();
}
}
こんにちは、Taroです。20歳です。
補足
Person p;
だけではインスタンスは作られません。p
は参照を入れる箱に過ぎず、new Person()
で初めて中身が用意されます。- 1つのクラスから何個でもインスタンスを作れます。それぞれは独立したデータを持ちます。
new演算子の役割とインスタンスの寿命
new
は主に次の役割を持ちます。
- 管理ヒープにメモリを確保し、クラスの初期化処理(コンストラクタ)を実行する。
- そのオブジェクトへの参照を返す。
C#のガベージコレクタ(GC)が、到達不能になったインスタンスを自動的に回収します。
重要なのは「変数の寿命」と「オブジェクトの寿命」は一致しないことです。
変数のスコープを抜けても、他の場所に参照が残っていればオブジェクトは生き続けます。
using System;
public class Person
{
public string Name { get; set; } = "NoName";
}
public class Program
{
// Personを生成して返すヘルパー
static Person CreatePerson(string name)
{
// newはヒープにオブジェクトを作り、その参照を返す
return new Person { Name = name };
}
public static void Main()
{
Person outer = CreatePerson("Hanako"); // CreatePersonのローカル変数は破棄されるが、オブジェクトはouterが参照しているので生存
Console.WriteLine(outer.Name);
// outerがnullになり、他に誰も参照していなければ、いずれGCで回収される
outer = null;
Console.WriteLine("参照を手放しました。必要ならGCが後で回収します。");
}
}
Hanako
参照を手放しました。必要ならGCが後で回収します。
newが必要な型(参照型)と不要な型(値型)
C#には大きく値型と参照型があります。
インスタンス化ではこの違いを知っておくと混乱しません。
- 参照型は
new
でヒープ上にオブジェクトを作ります。 - 値型は
new
なしでも変数自体が実体です。new
を付けると「既定値で初期化して使う」意味になります。
次の表は代表例です。
種別 | 例 | newの要否 |
---|---|---|
参照型 | class, array, string, List<T>, Dictionary<TKey,TValue> など | 必要(ただしstring はリテラルで生成されることも多い) |
値型 | int, double, bool, struct, enum, DateTime など | 不要(使う前に値を代入)。new を付けると既定値で初期化 |
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
// 値型(int)はnew不要
int a = 123;
// newを付けると0で初期化される
int b = new int(); // 0
// 参照型(string)はリテラルでも生成できる(実際はランタイムが用意)
string s1 = "Hello";
// newで生成することもある(例: 文字の繰り返し)
string s2 = new string('A', 5); // "AAAAA"
// 参照型(List<T>)はnewが必要
List<int> nums = new List<int>();
Console.WriteLine($"a={a}, b={b}, s1={s1}, s2={s2}, nums.Count={nums.Count}");
}
}
a=123, b=0, s1=Hello, s2=AAAAA, nums.Count=0
newの書き方とオブジェクト生成パターン
new クラス名() の基本構文
最も基本的な構文はnew クラス名()
です。
プロパティの初期化には代入かオブジェクト初期化子を使います。
using System;
public class Person
{
public string Name { get; set; } = "unknown";
public int Age { get; set; } = 0;
}
public class Program
{
public static void Main()
{
// 1. 生成してから代入
Person p1 = new Person();
p1.Name = "Miki";
p1.Age = 18;
// 2. オブジェクト初期化子でまとめて
Person p2 = new Person { Name = "Ken", Age = 25 };
// 3. C# 9以降: 変数の型が明らかな場合は target-typed new
Person p3 = new() { Name = "Sara", Age = 30 };
Console.WriteLine($"{p1.Name}({p1.Age}) / {p2.Name}({p2.Age}) / {p3.Name}({p3.Age})");
}
}
Miki(18) / Ken(25) / Sara(30)
メモ
- オブジェクト初期化子は、必須項目をコンストラクタで、任意項目を初期化子で、のように使い分けると読みやすくなります。
var
は右辺から型が推論できる場合に使えますが、学習初期は明示的な型を書くと混乱しにくいです。
引数付きnew(コンストラクタ呼び出し)
コンストラクタに引数を持たせると、生成時に必須の情報を渡せます。
using System;
public class Person
{
public string Name { get; }
public int Age { get; }
// 引数付きコンストラクタ(必須情報を強制できる)
public Person(string name, int age)
{
Name = name;
Age = age;
}
public override string ToString() => $"{Name}({Age})";
}
public class Program
{
public static void Main()
{
// 引数付きのnewで生成
var p = new Person("Akira", 22);
Console.WriteLine(p);
}
}
Akira(22)
ヒント
- コンストラクタを複数定義して「最低限必要な引数の組み合わせ」を提供すると、生成時のミスを減らせます。
- オプション値はオブジェクト初期化子と併用すると柔軟です。
配列をnewで生成する
配列はnew 型[長さ]
で生成します。
値型配列の各要素は既定値で初期化されますが、参照型配列は各要素がnull
で入ります。
using System;
public class Person
{
public string Name { get; set; } = "unknown";
public override string ToString() => Name;
}
public class Program
{
public static void Main()
{
// 値型の配列は既定値(0など)で埋まる
int[] numbers = new int[3]; // {0,0,0}
Console.WriteLine(string.Join(",", numbers)); // 0,0,0
// 参照型の配列は要素がnull
Person[] people = new Person[2]; // {null, null}
// nullが入っていることを確認
Console.WriteLine(people[0] == null ? "people[0] is null" : "not null");
// 各要素をnewしてから使う
people[0] = new Person { Name = "Taro" };
people[1] = new Person { Name = "Hanako" };
// オーバーロード解決のため、object[]に強制キャスト
Console.WriteLine(string.Join(", ", (object[])people));
}
}
0,0,0
people[0] is null
Taro, Hanako
C#のnewでよくあるエラーと対処法
CS0246: 型や名前空間が見つからない
原因は主に次の2つです。
対象の名前空間をusing
していない、または参照(パッケージ/プロジェクト)を追加していないことです。
// NG: List<T>の名前空間をusingしていない
public class Program
{
public static void Main()
{
List<string> list = new List<string>(); // CS0246
}
}
error CS0246: 型または名前空間名 'List<>' が見つかりません(using ディレクティブまたはアセンブリ参照が不足しています)。
対処例は次の通りです。
// OK: usingを追加
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<string> list = new List<string>();
}
}
// OK: 完全修飾名を使う(using不要)
public class Program
{
public static void Main()
{
System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();
}
}
補足
NuGetパッケージが必要な型は、パッケージの参照がなければCS0246
になります。参照の追加も確認してください。
CS1729: コンストラクタが見つからない/引数が合わない
存在しない引数の組み合わせでnew
したときに出ます。
public class Car
{
public string Model { get; }
public Car(string model) { Model = model; }
}
public class Program
{
public static void Main()
{
var car = new Car(); // CS1729: 引数0のコンストラクタがない
}
}
error CS1729: 'Car' は引数 0 を指定して構築できません
対処は「正しい引数で呼ぶ」か「必要なら引数なしコンストラクタを追加する」です。
// 対処1: 既存のコンストラクタに合わせて引数を渡す
var car = new Car("Prius");
// 対処2: 引数なしコンストラクタを用意する
public class Car
{
public string Model { get; }
public Car() { Model = "unknown"; }
public Car(string model) { Model = model; }
}
CS0144: 抽象クラスやinterfaceはnewできない
abstract class
やinterface
は契約や共通動作の型であり、そのままでは実体化できません。
public abstract class Animal { public abstract void Speak(); }
public interface ILogger { void Log(string message); }
public class Program
{
public static void Main()
{
var a = new Animal(); // CS0144
var l = new ILogger(); // CS0144
}
}
error CS0144: 抽象クラスまたはインターフェイス 'Animal' のインスタンスを作成できません
具象クラスを作ってそれをnew
します。
interface
は実装クラスをnew
し、変数の型はinterface
にすると柔軟です。
using System;
public abstract class Animal { public abstract void Speak(); }
public class Dog : Animal { public override void Speak() => Console.WriteLine("Bow!"); }
public interface ILogger { void Log(string message); }
public class ConsoleLogger : ILogger { public void Log(string message) => Console.WriteLine(message); }
public class Program
{
public static void Main()
{
Animal dog = new Dog();
dog.Speak();
ILogger logger = new ConsoleLogger();
logger.Log("Hello");
}
}
Bow!
Hello
CS0712: 静的クラスはnewできない
static class
はインスタンスを持ちません。
public static class MathUtil
{
public static int Add(int a, int b) => a + b;
}
public class Program
{
public static void Main()
{
var m = new MathUtil(); // CS0712
}
}
error CS0712: 静的クラス 'MathUtil' のインスタンスを作成できません
静的メンバーはクラス名から直接呼びます。
int sum = MathUtil.Add(1, 2); // OK
CS0122: アクセス修飾子が原因でnewできない
クラスやコンストラクタがprivate
やinternal
などでアクセス不可な場合に出ます。
よくあるのはコンストラクタがprivate
で外からnew
できないケースです。
public class User
{
public string Name { get; }
private User(string name) { Name = name; } // 外部からは呼べない
}
public class Program
{
public static void Main()
{
var u = new User("Alice"); // CS0122
}
}
error CS0122: 'User.User(string)' は保護レベルのためアクセスできません
対処は「アクセス可能なコンストラクタを公開する」か「公開されたファクトリメソッドを使う」です。
// 対処1: publicコンストラクタを用意
public class User
{
public string Name { get; }
public User(string name) { Name = name; }
}
// 対処2: ファクトリに限定
public class User
{
public string Name { get; }
private User(string name) { Name = name; }
public static User Create(string name) => new User(name); // これだけ公開
}
CS0120: インスタンスなしでメンバーを呼ぶエラー
インスタンスメンバーをクラス名から直接呼ぶと発生します。
public class Counter
{
public int Value { get; private set; }
public void Inc() => Value++;
}
public class Program
{
public static void Main()
{
Counter.Inc(); // CS0120: インスタンスが必要
}
}
error CS0120: 非静的なフィールド、メソッド、またはプロパティ 'Counter.Inc()' にオブジェクト参照が必要です
new
でインスタンスを作ってから呼びましょう。
あるいは設計上インスタンス不要ならstatic
にします。
// 対処1: インスタンスを作って呼ぶ
var c = new Counter();
c.Inc();
// 対処2: 静的メンバーにする(設計上インスタンス不要な場合のみ)
public class Counter
{
public static int Total { get; private set; }
public static void Inc() => Total++;
}
NullReferenceExceptionはnew忘れに注意
実行時によく遭遇するのがNullReferenceException
です。
参照型の変数がnull
なのにメンバーへアクセスしたときに発生します。
配列の各要素がnull
である点は特に注意です。
using System;
public class Person
{
public string Name { get; set; } = "";
}
public class Program
{
public static void Main()
{
// 参照型配列の各要素はnullで初期化される
Person[] people = new Person[1];
// 下行はpeople[0]がnullのため実行時例外
Console.WriteLine(people[0].Name);
}
}
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
正しくは各要素をnew
してから使います。
安全に扱う補助として?.
や??
演算子も役立ちます。
// 正しい初期化
Person[] people = new Person[1];
people[0] = new Person { Name = "Miki" };
Console.WriteLine(people[0].Name); // OK
// 安全にアクセス(people[0]がnullなら"unknown"を使う)
string name = people[0]?.Name ?? "unknown";
インスタンス化のベストプラクティス
new前に型と名前空間を確認する
インスタンス化でCS0246
が出るときは、まず対象型がどの名前空間かを確認し、using
があるか、必要な参照(NuGetパッケージやプロジェクト参照)が追加されているかを見直します。
迷ったら一時的に完全修飾名(例: System.Text.StringBuilder
)で書くと原因の切り分けができます。
コンストラクタ引数と既定値を確認する
コンストラクタのシグネチャに合った引数が必要です。
最低限の必須情報はコンストラクタで受け取り、任意の設定はオブジェクト初期化子で与えると読みやすくなります。
引数が多い場合は名前付き引数を使うと見間違いを防げます。
var user = new Person(name: "Akira", age: 22); // 名前付き引数で可読性向上
参照がnullでないことを確認する
参照型を使う前には必ずnew
しているかを確認します。
メソッドの先頭でガード節を置くのは有効です。
void Print(Person person)
{
if (person == null) throw new ArgumentNullException(nameof(person));
Console.WriteLine(person.Name);
}
状況に応じて?.
や??
を使い、NullReferenceException
を未然に防ぎます。
ただし!
(null許容性抑止)は最後の手段で、正しく初期化する設計が優先です。
まとめ
new
はC#でインスタンスを作るための基本中の基本です。
クラスは設計図、new
は実体化、参照型はnew
が必要、値型はnew
不要という整理が第一歩になります。
配列では参照型要素がnull
である点を忘れず、各要素をnew
してから使うことが重要です。
よくあるエラーは、名前空間の不足、コンストラクタの不一致、抽象型や静的クラスの誤用、アクセス修飾子の制約、インスタンスなし呼び出し、そしてnull
参照が原因です。
生成時は型と名前空間、コンストラクタの引数、null
の可能性を順に確認すれば、多くのつまずきを避けられます。
実装では「必須情報はコンストラクタ、任意は初期化子」「使う前にnew
」「null
は早めに検知」の方針を徹底して、安全で読みやすいコードを目指しましょう。