C#でインスタンス(オブジェクト)を作る方法は非常に重要です。
特にnew演算子の使い方を理解すると、クラスの利用、プロパティの初期化、コレクションの操作などが自然にできるようになります。
本記事では、newでのインスタンス化の基本からよくあるエラーの原因と対策まで、初心者の方がつまずきやすいポイントを丁寧に解説します。
newでインスタンス化する基本
インスタンス(オブジェクト)とは
インスタンスとは、クラスという設計図から実体を作ったものです。
クラスは「どんなデータを持ち、どんな振る舞いがあるか」を定義し、インスタンスが実際の値を保持します。
C#では多くの型が参照型(classやstringなど)であり、new
でメモリ上に領域を確保して参照を変数に保持します。
一方で値型(intやstructなど)は値そのものを変数が持ちます。
newの基本構文と書き方
もっとも基本的な書き方は次のとおりです。
- 明示的な型で受ける:
型名 変数名 = new 型名(引数);
- varで型推論する:
var 変数名 = new 型名(引数);
- ジェネリック型:
var list = new List<int>();
右辺のnewでインスタンスが生成され、左辺がその参照を受け取ります。
C# 9以降ではコンテキストから型が明らかな場合にnew()
とだけ書けるターゲット型のnewも利用できます。
クラスの最小例とインスタンス生成
短いクラスを定義し、インスタンスを生成して使ってみます。
using System;
// 最小限のクラス定義とインスタンス化の例
public class Person
{
// 自動実装プロパティ。名前と年齢を保持します
public string Name { get; set; } = ""; // 既定値でnullを避ける
public int Age { get; set; } // 値型は既定で0
// 何もしない引数なしコンストラクタは省略可能
}
public class Program
{
public static void Main()
{
// newでインスタンス化し、プロパティを設定して利用します
var p = new Person();
p.Name = "Taro";
p.Age = 20;
Console.WriteLine($"{p.Name} は {p.Age} 歳です");
}
}
Taro は 20 歳です
コンストラクタを呼び出すnew
コンストラクタはインスタンス生成時に初期化を行う特別なメソッドです。
new
に引数を渡すと、そのシグネチャに一致するコンストラクタが呼び出されます。
using System;
public class Book
{
public string Title { get; }
public int Pages { get; }
// 引数ありコンストラクタ。生成と同時に不変な状態を作る
public Book(string title, int pages)
{
Title = title;
Pages = pages;
}
}
public class Program
{
public static void Main()
{
var b = new Book("Effective C#", 350);
Console.WriteLine($"{b.Title} / {b.Pages} pages");
}
}
Effective C# / 350 pages
オブジェクト初期化子でプロパティを同時設定
オブジェクト初期化子を使うと、new直後にプロパティやフィールドを一括設定できます。
読みやすく、nullの混入も防ぎやすくなります。
using System;
public class User
{
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}
public class Program
{
public static void Main()
{
// オブジェクト初期化子でプロパティをまとめて設定
var user = new User
{
Name = "Hanako",
Email = "hanako@example.com"
};
Console.WriteLine($"{user.Name} <{user.Email}>");
}
}
Hanako <hanako@example.com>
代表的なインスタンス生成パターン
引数なしコンストラクタのnew
最初に空の状態を作り、あとからプロパティを設定するパターンです。
UIバインディングやシリアライザと相性が良い場面があります。
var p = new Person(); // 引数なし
p.Name = "Jiro";
p.Age = 18;
引数ありコンストラクタのnew
必須情報の設定漏れを防ぎ、不変条件を守りやすくなります。
public class Account
{
public string Id { get; }
public decimal Balance { get; private set; }
public Account(string id, decimal initialBalance)
{
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("id");
if (initialBalance < 0) throw new ArgumentOutOfRangeException(nameof(initialBalance));
Id = id;
Balance = initialBalance;
}
}
var acc = new Account("A-001", 1000m);
コレクション(List<T>)のインスタンス化
コレクションは型引数が必須です。
初期要素を指定したり、容量を指定できます。
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
// 空で作ってから追加
var list1 = new List<string>();
list1.Add("apple");
list1.Add("banana");
// 初期要素をまとめて指定
var list2 = new List<string> { "cat", "dog" };
// 容量を先に確保して再割り当てを減らす
var list3 = new List<int>(capacity: 100);
list3.Add(42);
Console.WriteLine($"{string.Join(", ", list1)}");
Console.WriteLine($"{string.Join(", ", list2)}");
Console.WriteLine($"list3.Count = {list3.Count}");
}
}
apple, banana
cat, dog
list3.Count = 1
補足として、C# 9以降ではターゲット型が明確な場合にList<string> fruits = new();
のように省略できます。
stringはnew不要な参照型の例
stringは参照型ですが、文字列リテラル"text"
で直接書けます。
内部的には文字列インターンなどの最適化があり、ふつうはnew string(...)
を使いません。
string s1 = "hello"; // 推奨
string s2 = string.Empty; // 空文字を表す定数
string s3 = new string('a', 3); // 特殊な生成: "aaa"
リテラルで十分な場合はリテラルを使うのが読みやすく効率的です。
値型(struct)のnewと既定値
値型はnewしなくても既定値で使えます。
new
を使うとフィールドが既定値で初期化されます。
using System;
public struct Point
{
public int X;
public int Y;
}
public class Program
{
public static void Main()
{
Point p1; // 未割り当てのローカル変数は、使う前に全フィールドへ代入が必要
p1.X = 1; p1.Y = 2; // これで使用可能
Console.WriteLine($"p1=({p1.X},{p1.Y})");
Point p2 = new Point(); // すべて0で初期化
Console.WriteLine($"p2=({p2.X},{p2.Y})");
Point p3 = default; // これもすべて0
Console.WriteLine($"p3=({p3.X},{p3.Y})");
}
}
p1=(1,2)
p2=(0,0)
p3=(0,0)
よくあるエラーと対策
NullReferenceExceptionはnewし忘れが原因
参照型の変数がnullのままメンバーにアクセスすると発生します。
newで生成するか、nullを扱う分岐を入れます。
// 悪い例: listがnullのままAddしようとして落ちる
List<string> list = null!;
list.Add("x"); // 実行時にNullReferenceException
対策として、先にnew
するか、??=
で遅延生成します。
List<string> list = null;
// どこかで使う直前に生成
list ??= new List<string>();
list.Add("x");
Console.WriteLine(list.Count); // 1
1
CS7036: 引数が一致しないコンストラクタ呼び出し
呼び出した引数の並びや型が、定義されたコンストラクタと一致していません。
public class User
{
public User(string name, int age) { }
}
var u = new User(20, "Taro");
// CS7036: 引数の型/順序が一致しない
対策は正しい型と順序で渡すか、new User(name: "Taro", age: 20)
のように名前付き引数を使います。
var u1 = new User("Taro", 20);
var u2 = new User(age: 20, name: "Taro"); // 名前付きなら順序自由
CS1729: コンストラクタが見つからない
存在しないシグネチャを呼んでいます。
引数なしを呼んでいるが、定義がないなど。
public class Order
{
public Order(int id) { }
}
var o = new Order();
// CS1729: 'Order' に引数 0 のコンストラクタは含まれません
対策は、定義に合わせて呼ぶか、必要なコンストラクタを追加します。
var o = new Order(123); // または
public class Order
{
public Order() { } // 引数なしを追加
public Order(int id) { }
}
CS0144: 抽象クラスやinterfaceはnewできない
abstract classやinterfaceは契約だけなので直接newできません。
具象クラスをnewします。
public interface IAnimal { void Speak(); }
public abstract class Animal : IAnimal { public abstract void Speak(); }
public class Dog : Animal
{
public override void Speak() => Console.WriteLine("Woof");
}
IAnimal a1 = new Dog(); // OK
// IAnimal a2 = new IAnimal(); // CS0144
// var a3 = new Animal(); // CS0144
CS0122: コンストラクタがprivateでnewできない
シングルトンやファクトリ専用設計などでコンストラクタがprivateのとき、外部からnewできません。
public class Secret
{
private Secret() { }
}
// var s = new Secret(); // CS0122: アクセスできない
対策は、公開APIを通すことです。
public class Secret
{
private Secret() { }
public static Secret Create() => new Secret(); // ファクトリ
}
var s = Secret.Create(); // OK
CS0305: ジェネリック型の型引数不足(List<T>)
型引数<T>を忘れると発生します。
// var list = new List(); // CS0305: 型引数が必要
var list = new List<string>(); // OK
CS0246: 型や名前空間が見つからない(using忘れ)
必要な名前空間のusing
がない、参照を追加していない場合に発生します。
// List<> は System.Collections.Generic にある
using System.Collections.Generic;
List<int> x = new List<int>(); // OK
完全修飾名を使う方法もあります。
System.Collections.Generic.List<int>
CS0266: 型が一致せず暗黙変換できない
互換性のない型へ代入しています。
例えばList<object>
はList<string>
へ代入できません。
List<object> a = new List<object>();
// List<string> b = a; // CS0266: 代入不可
IEnumerable<object> c = new List<string>(); // 共変IEnumerableならOK
対策は型を合わせる、共変なインターフェースへ上げる、必要なら明示変換を行います。
安全で読みやすいインスタンス化のコツ
varと明示的型の使い分け
右辺で型が明白なときはvar
が読みやすいです。
推論しづらいときは明示的に書きます。
- 良い例:
var users = new List<User>();
型が右辺に現れて明確 - 明示したい例:
Dictionary<string, List<int>> map = new();
右辺省略時は左辺で示す
次の表は判断の目安です。
ケース | おすすめの書き方 | 理由 |
---|---|---|
右辺が明快なnew | var list = new List<string>(); | 型が一目で分かる |
複雑な型/右辺がメソッド呼び出し | IEnumerable<User> seq = GetUsers(); | 意図する型を明示 |
ターゲット型newを使う | User u = new(); | 右辺が簡潔で誤解がない |
必要なタイミングでnewする(遅延生成)
重いオブジェクトは必要になるまで生成しないとメモリや起動時間を節約できます。
簡易的には??=
、本格的にはLazy<T>
を使います。
// 簡易な遅延生成
List<string>? cache = null;
IEnumerable<string> GetItems()
{
cache ??= Load(); // 初回のみ生成
return cache;
}
List<string> Load() => new List<string> { "A", "B" };
using System;
Lazy<byte[]> big = new Lazy<byte[]>(() =>
{
Console.WriteLine("Allocate!");
return new byte[10_000_000];
});
Console.WriteLine("Before access");
_ = big.Value; // ここで初めて生成
Console.WriteLine("After access");
Before access
Allocate!
After access
nullを避けるための初期化パターン
nullのまま使ってしまう事故を避けるには、いくつかの初期化パターンが有効です。
- フィールド/プロパティに既定値を与える:
public string Name { get; set; } = "";
- コレクションは空で初期化:
public List<int> Items { get; } = new();
- コンストラクタで必須項目を受け取る
- 利用直前に
??=
で生成 - 受け取った可能性のあるnullには
??
でフォールバック
public class Cart
{
// 空リストで初期化し、nullを避ける
public List<string> Items { get; } = new();
public void Add(string item)
{
// itemがnullなら空文字にするなどの防御
var safe = item ?? "";
Items.Add(safe);
}
}
「とりあえずnull」から始めないのが、最終的に例外の少ないコードにつながります。
まとめ
本記事では、C#のnewによるインスタンス化の基本から、コンストラクタとオブジェクト初期化子の活用、List<T>などの代表的パターン、さらによくあるエラーと具体的な対処法まで解説しました。
特にNullReferenceExceptionはnewのし忘れや初期化不足が原因で起きやすいため、プロパティの既定値設定や??=
による遅延生成を取り入れると安全です。
varと明示的型のバランス、必要なタイミングでの生成、空コレクションで開始する習慣などを身につけることで、読みやすく堅牢なC#コードが書けるようになります。