C#のnewとは?インスタンス生成・隠蔽・初期化の使い方を初心者向けに徹底解説

はじめに

C#を学び始めると、かなり早い段階で new というキーワードに出会います。

C#
Person person = new Person();

このコードを見ると、「new は何かを作っているらしい」と感じるはずです。実際、C#における new のもっとも基本的な役割は、クラスや構造体などの型から新しいインスタンスを生成することです。

ただし、C#の new はそれだけではありません。

同じ new でも、使われる場所によって意味が変わります。インスタンスを作る new、継承元のメンバーを隠す new、ジェネリックで引数なしコンストラクターを要求する new() 制約など、複数の使い方があります。MicrosoftのC#リファレンスでも、new キーワードは「演算子」「修飾子」「制約」として使われると説明されています。Microsoft Learn

この記事では、C#初心者がつまずきやすい new の意味を、インスタンス生成、初期化、隠蔽、override との違い、ジェネリック制約まで順番に解説します。

1. C#のnewとは?初心者が最初に押さえる3つの意味

C#の new は、ひとことで言えば「新しいものを作る、または新しい定義であることを示す」キーワードです。

ただし、C#では new が使われる文脈によって役割が変わります。初心者のうちは、まず次の3つを押さえておくと理解しやすくなります。

1つ目は、インスタンスを生成する new です。

C#
User user = new User();

2つ目は、継承元のメンバーを隠す new 修飾子です。

C#
class Child : Parent
{
public new void Show()
{
Console.WriteLine("Child");
}
}

3つ目は、ジェネリックで使う new() 制約です。

C#
class Factory<T> where T : new()
{
public T Create()
{
return new T();
}
}

このように、同じ new でも「何をしているのか」はコードの位置によって異なります。

1-1. newは「新しいインスタンスを作る」ためのキーワード

もっともよく使う new は、クラスから新しいオブジェクトを作るためのものです。

C#
Car car = new Car();

このコードでは、Car というクラスをもとに、新しい Car オブジェクトを作成しています。作成されたオブジェクトは、変数 car から操作できます。

たとえば次のようなクラスがあるとします。

C#
class Car
{
public string Name { get; set; }

public void Run()
{
Console.WriteLine($"{Name}が走ります");
}
}

このクラスは、あくまで「設計図」です。実際に使うには、new を使ってインスタンスを作ります。

C#
Car car = new Car();
car.Name = "プリウス";
car.Run();

実行結果は次のようになります。

C#
プリウスが走ります

つまり、new はクラスという設計図から、実際に使えるオブジェクトを作るためのキーワードです。

1-2. newには「演算子」「修飾子」「制約」の意味がある

C#の new には、大きく分けて3つの意味があります。

まず、new 演算子です。

C#
var user = new User();

これは、新しいインスタンスを作るために使います。C#の公式リファレンスでも、new 演算子は型の新しいインスタンスを作成すると説明されています。Microsoft Learn

次に、new 修飾子です。

C#
class Child : Parent
{
public new void Display()
{
Console.WriteLine("Child Display");
}
}

これは、継承元のクラスにあるメンバーと同じ名前のメンバーを派生クラスで定義し、意図的に隠すために使います。

最後に、new() 制約です。

C#
class Repository<T> where T : new()
{
public T Create()
{
return new T();
}
}

これは、ジェネリック型 T に対して「引数なしコンストラクターを持つ型だけを使える」と制限するために使います。

1-3. 検索で混乱しやすい「new」の使い分け一覧

c# new で検索すると、さまざまな意味の new が出てきます。初心者が混乱しやすいのは、同じ単語がまったく違う場面で使われるからです。

使い方意味
new 演算子new Person()インスタンスを生成する
オブジェクト初期化子new Person { Name = "Tom" }生成と同時にプロパティを設定する
配列生成new int[3]配列を作る
匿名型new { Name = "Tom" }名前のない型を作る
target-typed newPerson p = new();型名を省略して生成する
new 修飾子public new void Show()継承元のメンバーを隠す
new() 制約where T : new()引数なしコンストラクターを要求する

最初はすべてを一度に覚える必要はありません。まずは「インスタンス生成の new」を理解し、その後に「隠蔽の new」「制約の new()」を学ぶとスムーズです。

1-4. この記事で学べるnewの全体像

この記事では、C#の new を次の流れで解説します。

まず、基本となるインスタンス生成の仕組みを説明します。次に、クラス、構造体、配列、匿名型、target-typed newなど、実際の書き方を紹介します。

その後、オブジェクト初期化子やコレクション初期化子を使った初期化方法を解説します。さらに、継承で使う new 修飾子、override との違い、ジェネリックで使う new() 制約についても扱います。

最後に、varnull、ガベージコレクション、DIやファクトリーパターンとの関係まで整理します。

2. new演算子の基本:インスタンス生成の仕組み

C#で new を理解するうえで最初に大切なのは、「クラス」と「インスタンス」の違いです。

クラスは設計図です。インスタンスは、その設計図から作られた実体です。

たとえば Person クラスは「人を表す設計図」であり、new Person() によって作られる person は「実際に使える人オブジェクト」です。

2-1. インスタンスとは何か

インスタンスとは、クラスや構造体などの型から作られた実体のことです。

次のコードを見てください。

C#
class Person
{
public string Name { get; set; }

public void Greet()
{
Console.WriteLine($"こんにちは、{Name}です");
}
}

この Person クラスだけでは、まだ具体的な人物は存在しません。Person という型の定義があるだけです。

実際に使うには、次のように new します。

C#
Person person = new Person();
person.Name = "佐藤";
person.Greet();

この person がインスタンスです。

クラスを「たい焼きの型」と考えると、インスタンスは「型から作られたたい焼き」です。同じクラスから、複数のインスタンスを作ることもできます。

C#
Person person1 = new Person();
person1.Name = "佐藤";

Person person2 = new Person();
person2.Name = "鈴木";

person1.Greet();
person2.Greet();

実行結果は次のようになります。

C#
こんにちは、佐藤です
こんにちは、鈴木です

person1person2 は、同じ Person クラスから作られていますが、別々のインスタンスです。

2-2. クラスからオブジェクトを作る基本構文

クラスからオブジェクトを作る基本構文は次のとおりです。

C#
型名 変数名 = new 型名();

具体例は次のようになります。

C#
Person person = new Person();

左側の Person person は、「Person 型の変数 person を用意する」という意味です。

右側の new Person() は、「Person クラスの新しいインスタンスを作る」という意味です。

作成されたインスタンスは、変数に代入されます。

C#
person.Name = "田中";
person.Greet();

このように、new で作ったオブジェクトは、変数を通じてプロパティやメソッドを使えます。

2-3. newでコンストラクターが呼び出される流れ

new を使うと、対象の型のコンストラクターが呼び出されます。

コンストラクターとは、インスタンスが作られるときに実行される特別なメソッドです。

C#
class Person
{
public string Name { get; set; }

public Person()
{
Name = "未設定";
Console.WriteLine("Personのコンストラクターが呼ばれました");
}
}

このクラスを new します。

C#
Person person = new Person();
Console.WriteLine(person.Name);

実行結果は次のようになります。

C#
Personのコンストラクターが呼ばれました
未設定

new Person() が実行されると、メモリ上に新しいインスタンスが作られ、その直後に Person() コンストラクターが呼ばれます。

引数ありコンストラクターを定義することもできます。

C#
class Person
{
public string Name { get; }

public Person(string name)
{
Name = name;
}
}

この場合は、new するときに引数を渡します。

C#
Person person = new Person("山田");
Console.WriteLine(person.Name);

2-4. 参照型と値型でnewの意味はどう変わるか

C#には、大きく分けて参照型と値型があります。

クラスは参照型です。構造体や intdoublebool などは値型です。

参照型を new すると、インスタンスが作られ、その場所を参照する値が変数に入ります。

C#
Person person = new Person();

このとき、変数 person にはオブジェクトそのものではなく、オブジェクトを参照するための情報が入っています。

一方、値型でも new は使えます。

C#
int number = new int();
Console.WriteLine(number);

実行結果は次のようになります。

C#
0

int の既定値は 0 です。値型で new を使うと、基本的にはその型の既定値で初期化されます。

構造体の場合も同様です。

C#
struct Point
{
public int X;
public int Y;
}

Point point = new Point();
Console.WriteLine(point.X);
Console.WriteLine(point.Y);

実行結果は次のようになります。

C#
0
0

クラスの new は「参照先となるオブジェクトを作る」と考えると分かりやすく、値型の new は「値を初期状態にする」と考えると理解しやすいです。

2-5. newしたオブジェクトはメモリ上でどう扱われるか

クラスのインスタンスを new すると、プログラムの実行中に使われるメモリ領域にオブジェクトが作られます。

C#
Person person = new Person();

このとき、new Person() によって Person オブジェクトが作られ、変数 person はそのオブジェクトを参照します。

重要なのは、変数に入っているのはオブジェクトそのものではなく、オブジェクトへの参照だという点です。

次のコードを見てください。

C#
Person person1 = new Person();
person1.Name = "佐藤";

Person person2 = person1;
person2.Name = "鈴木";

Console.WriteLine(person1.Name);

実行結果は次のようになります。

C#
鈴木

person2 = person1 は、オブジェクトをコピーしているのではありません。同じインスタンスを参照する変数が2つになっただけです。

そのため、person2.Name を変更すると、同じオブジェクトを参照している person1.Name も変更されたように見えます。

3. C#のnewを使ったインスタンス生成の書き方

ここからは、C#で実際によく使う new の書き方を見ていきます。

クラスのインスタンス生成、引数ありコンストラクター、構造体、配列、匿名型、target-typed newを順番に確認します。

3-1. クラスのインスタンスを生成する

クラスのインスタンス生成は、もっとも基本的な new の使い方です。

C#
class Product
{
public string Name { get; set; }
public int Price { get; set; }
}

このクラスからインスタンスを作るには、次のように書きます。

C#
Product product = new Product();

product.Name = "ノートPC";
product.Price = 120000;

Console.WriteLine(product.Name);
Console.WriteLine(product.Price);

new Product() によって、新しい Product インスタンスが作られます。

同じクラスから複数のインスタンスを作ることもできます。

C#
Product product1 = new Product();
product1.Name = "マウス";
product1.Price = 3000;

Product product2 = new Product();
product2.Name = "キーボード";
product2.Price = 8000;

product1product2 は、どちらも Product 型ですが、別々のインスタンスです。

3-2. 引数ありコンストラクターで初期値を渡す

コンストラクターに引数を用意すると、new のタイミングで初期値を渡せます。

C#
class Product
{
public string Name { get; }
public int Price { get; }

public Product(string name, int price)
{
Name = name;
Price = price;
}
}

このクラスは、NamePrice をコンストラクターで受け取ります。

C#
Product product = new Product("モニター", 30000);

Console.WriteLine(product.Name);
Console.WriteLine(product.Price);

実行結果は次のようになります。

C#
モニター
30000

この書き方のメリットは、インスタンス生成時に必要な値を必ず渡せることです。

たとえば、商品名や価格がない Product を作りたくない場合、引数ありコンストラクターを使うと安全です。

C#
Product product = new Product("デスク", 20000);

一方、次のように引数を省略すると、対応する引数なしコンストラクターがないためエラーになります。

C#
Product product = new Product(); // エラー

3-3. 構造体をnewで生成する

構造体も new で生成できます。

C#
struct Point
{
public int X;
public int Y;
}

構造体を new する例です。

C#
Point point = new Point();

Console.WriteLine(point.X);
Console.WriteLine(point.Y);

実行結果は次のようになります。

C#
0
0

構造体のフィールドは、既定値で初期化されます。int の既定値は 0 なので、XY はどちらも 0 になります。

構造体にもコンストラクターを定義できます。

C#
struct Point
{
public int X;
public int Y;

public Point(int x, int y)
{
X = x;
Y = y;
}
}

この場合は、次のように初期値を渡せます。

C#
Point point = new Point(10, 20);

Console.WriteLine(point.X);
Console.WriteLine(point.Y);

3-4. 配列をnewで生成する

配列を作るときにも new を使います。

C#
int[] numbers = new int[3];

このコードでは、int 型の要素を3つ持つ配列を作成しています。

C#
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

Console.WriteLine(numbers[0]);

配列は、作成時に要素数を指定します。

C#
string[] names = new string[2];

names[0] = "佐藤";
names[1] = "鈴木";

初期値を同時に指定することもできます。

C#
int[] scores = new int[] { 80, 90, 100 };

さらに、型を省略して次のようにも書けます。

C#
int[] scores = { 80, 90, 100 };

ただし、変数宣言と同時ではなく代入する場合は、基本的に new が必要です。

C#
int[] scores;
scores = new int[] { 80, 90, 100 };

3-5. 匿名型をnewで生成する

C#では、クラス名を明示的に定義せずに一時的なオブジェクトを作ることができます。これを匿名型と呼びます。

C#
var user = new { Name = "田中", Age = 30 };

Console.WriteLine(user.Name);
Console.WriteLine(user.Age);

匿名型は、LINQの結果を一時的にまとめたいときによく使われます。

C#
var product = new
{
Name = "スマートフォン",
Price = 90000,
InStock = true
};

匿名型では、プロパティ名と値を new { ... } の中に書きます。

C#
var item = new { Id = 1, Title = "C#入門" };

この item の型名はコード上に現れません。そのため、通常は var と組み合わせて使います。

匿名型は便利ですが、メソッドの戻り値として広く使い回す用途には向いていません。基本的には、メソッド内の一時的なデータ表現として使うのが分かりやすいです。

3-6. target-typed newでコードを短く書く

C# 9以降では、target-typed newという書き方が使えます。これは、左辺や文脈から型が分かる場合に、右辺の型名を省略できる機能です。Microsoftのドキュメントでも、MyClass x = new(); のように、コンパイラーが文脈から型を推論できる場合に型名を省略できると説明されています。Microsoft Learn

通常の書き方は次のとおりです。

C#
Person person = new Person();

target-typed newを使うと、次のように書けます。

C#
Person person = new();

左辺に Person と書かれているため、右辺の new()Person のインスタンス生成だと分かります。

引数ありコンストラクターでも使えます。

C#
Product product = new("マウス", 3000);

ただし、型が推論できない場面では使えません。

C#
var person = new(); // エラー

var は右辺から型を推論します。しかし、右辺が new() だけだと型が分からないため、コンパイルエラーになります。

target-typed newは便利ですが、初心者のうちは通常の new ClassName() を先に理解することをおすすめします。コードの意味が明確になるからです。

4. newと初期化:オブジェクト初期化子の使い方

new でインスタンスを作ったあと、プロパティに値を設定するコードはよく出てきます。

C#
Person person = new Person();
person.Name = "佐藤";
person.Age = 25;

このような処理は、オブジェクト初期化子を使うと簡潔に書けます。

C#
Person person = new Person
{
Name = "佐藤",
Age = 25
};

C#では、インスタンス生成と初期値設定を組み合わせるための構文が用意されています。

4-1. オブジェクト初期化子とは

オブジェクト初期化子とは、new でオブジェクトを作ると同時に、プロパティやフィールドへ値を設定する構文です。

次のクラスを例にします。

C#
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

通常の書き方は次のとおりです。

C#
Person person = new Person();
person.Name = "田中";
person.Age = 30;

オブジェクト初期化子を使うと、次のように書けます。

C#
Person person = new Person
{
Name = "田中",
Age = 30
};

この書き方では、インスタンス生成と初期化がひとまとまりになるため、コードが読みやすくなります。

4-2. new クラス名 { プロパティ = 値 } の基本構文

オブジェクト初期化子の基本構文は次のとおりです。

C#
型名 変数名 = new 型名
{
プロパティ名 = ,
プロパティ名 =
};

具体例です。

C#
Book book = new Book
{
Title = "C#入門",
Price = 2500
};

クラスは次のように定義されているとします。

C#
class Book
{
public string Title { get; set; }
public int Price { get; set; }
}

new Book の後ろに { ... } を書き、その中でプロパティに値を設定します。

なお、引数なしコンストラクターを呼び出す場合、オブジェクト初期化子では () を省略できます。

C#
Book book1 = new Book()
{
Title = "C#入門",
Price = 2500
};

Book book2 = new Book
{
Title = "C#入門",
Price = 2500
};

どちらもよく使われます。

4-3. コンストラクター初期化とオブジェクト初期化子の違い

コンストラクター初期化とオブジェクト初期化子は、どちらも初期値を設定する方法ですが、考え方が違います。

コンストラクター初期化は、インスタンス生成に必要な値を強制したいときに向いています。

C#
class User
{
public string Name { get; }

public User(string name)
{
Name = name;
}
}

このクラスでは、User を作るときに必ず name を渡す必要があります。

C#
User user = new User("佐藤");

一方、オブジェクト初期化子は、任意のプロパティを分かりやすく設定したいときに向いています。

C#
class User
{
public string Name { get; set; }
public int Age { get; set; }
}
C#
User user = new User
{
Name = "佐藤",
Age = 25
};

重要な値や必須の値はコンストラクターで受け取り、任意の設定値はオブジェクト初期化子で指定する、という使い分けがよく使われます。

4-4. コレクション初期化子の使い方

リストなどのコレクションも、new と同時に初期化できます。

C#
List<string> names = new List<string>
{
"佐藤",
"鈴木",
"田中"
};

これは、次のように Add を何度も呼び出すコードを簡潔にしたものです。

C#
List<string> names = new List<string>();
names.Add("佐藤");
names.Add("鈴木");
names.Add("田中");

数値のリストでも同じです。

C#
List<int> scores = new List<int>
{
80,
90,
100
};

オブジェクトのリストも初期化できます。

C#
List<Person> people = new List<Person>
{
new Person { Name = "佐藤", Age = 25 },
new Person { Name = "鈴木", Age = 30 }
};

target-typed newと組み合わせると、次のようにも書けます。

C#
List<Person> people = new()
{
new Person { Name = "佐藤", Age = 25 },
new Person { Name = "鈴木", Age = 30 }
};

さらに、C# 12以降ではコレクション式というより簡潔な初期化構文も導入されていますが、初心者のうちはまず new List<T> { ... } の形を理解しておくとよいでしょう。Microsoft Learn

4-5. initプロパティとrequiredメンバーの初期化

C#には、初期化時だけ設定できる init プロパティがあります。

C#
class Person
{
public string Name { get; init; }
public int Age { get; init; }
}

init プロパティは、オブジェクト初期化子で設定できます。

C#
Person person = new Person
{
Name = "佐藤",
Age = 25
};

しかし、生成後に変更しようとするとエラーになります。

C#
person.Name = "鈴木"; // エラー

init は、初期化後に値を変えたくないデータを表現するときに便利です。

また、C# 11以降では required 修飾子も使えます。required は、そのフィールドやプロパティがオブジェクト初期化時に必ず設定される必要があることを示します。Microsoft Learn

C#
class Person
{
public required string Name { get; init; }
public int Age { get; init; }
}

この場合、次のコードは問題ありません。

C#
Person person = new Person
{
Name = "佐藤",
Age = 25
};

しかし、Name を設定しないとエラーになります。

C#
Person person = new Person
{
Age = 25
}; // エラー

required を使うと、オブジェクト初期化子を使いながら、必要な値の設定漏れを防げます。

4-6. 初期化でよくあるコンパイルエラー

オブジェクト初期化子では、いくつかのエラーが起きやすいです。

まず、存在しないプロパティを指定するとエラーになります。

C#
Person person = new Person
{
FullName = "佐藤" // エラー
};

Person クラスに FullName プロパティがなければ、コンパイルできません。

次に、読み取り専用プロパティに代入しようとするとエラーになります。

C#
class Person
{
public string Name { get; }
}
C#
Person person = new Person
{
Name = "佐藤" // エラー
};

get だけのプロパティは、オブジェクト初期化子では設定できません。ただし、init が付いていれば初期化時に設定できます。

C#
class Person
{
public string Name { get; init; }
}

また、required メンバーを設定し忘れるとエラーになります。

C#
class Product
{
public required string Name { get; init; }
}
C#
Product product = new Product(); // エラー

この場合は、次のように書きます。

C#
Product product = new Product
{
Name = "ノートPC"
};

5. new修飾子とは?メンバー隠蔽の使い方

C#の new には、インスタンス生成とは別に、メンバーを隠蔽するための使い方があります。

これは new 修飾子と呼ばれます。

C#
class Child : Parent
{
public new void Show()
{
Console.WriteLine("Child");
}
}

new 修飾子は、継承元のクラスにあるメンバーと同じ名前のメンバーを派生クラスで定義するときに使います。

5-1. new修飾子は「継承元のメンバーを隠す」ために使う

new 修飾子は、基底クラスのメンバーを派生クラス側で明示的に隠すために使います。MicrosoftのC#リファレンスでも、宣言修飾子として new を使うと、基底クラスが提供するメンバーを明示的に隠すと説明されています。Microsoft Learn

次のコードを見てください。

C#
class Parent
{
public void Show()
{
Console.WriteLine("Parent.Show");
}
}

class Child : Parent
{
public new void Show()
{
Console.WriteLine("Child.Show");
}
}

Parent にも Show があり、Child にも Show があります。

このとき、ChildShowParentShow を上書きしているのではなく、隠しています。

C#
Child child = new Child();
child.Show();

実行結果は次のようになります。

C#
Child.Show

Child 型の変数から呼び出しているため、Child 側の Show が呼ばれます。

5-2. メソッドをnewで隠蔽する例

メソッドの隠蔽をもう少し詳しく見てみましょう。

C#
class Animal
{
public void Speak()
{
Console.WriteLine("Animal speaks");
}
}

class Dog : Animal
{
public new void Speak()
{
Console.WriteLine("Dog barks");
}
}

この場合、Dog クラスの Speak は、Animal クラスの Speak を隠しています。

C#
Dog dog = new Dog();
dog.Speak();

実行結果は次のようになります。

C#
Dog barks

ただし、変数の型を Animal にすると結果が変わります。

C#
Animal animal = new Dog();
animal.Speak();

実行結果は次のようになります。

C#
Animal speaks

実体は Dog ですが、変数の型が Animal なので、隠蔽では Animal 側のメソッドが呼ばれます。

ここが new 修飾子の重要なポイントです。

5-3. プロパティやフィールドをnewで隠蔽する例

new 修飾子は、メソッドだけでなく、プロパティやフィールドにも使えます。

プロパティを隠蔽する例です。

C#
class BaseUser
{
public string Name { get; set; } = "BaseUser";
}

class AdminUser : BaseUser
{
public new string Name { get; set; } = "AdminUser";
}

使ってみます。

C#
AdminUser admin = new AdminUser();
Console.WriteLine(admin.Name);

BaseUser user = admin;
Console.WriteLine(user.Name);

実行結果は次のようになります。

C#
AdminUser
BaseUser

同じインスタンスを参照していても、変数の型によって見えるプロパティが変わります。

フィールドでも同様です。

C#
class Parent
{
public int Value = 1;
}

class Child : Parent
{
public new int Value = 2;
}
C#
Child child = new Child();
Console.WriteLine(child.Value);

Parent parent = child;
Console.WriteLine(parent.Value);

実行結果は次のようになります。

C#
2
1

5-4. new修飾子を付けない場合の警告

基底クラスと同じ名前のメンバーを派生クラスで定義すると、new 修飾子を付けなくてもコンパイル自体はできる場合があります。

C#
class Parent
{
public void Show()
{
Console.WriteLine("Parent");
}
}

class Child : Parent
{
public void Show()
{
Console.WriteLine("Child");
}
}

しかし、この場合はコンパイラーから警告が出ます。

「継承されたメンバーを隠しています。隠すつもりなら new キーワードを使用してください」という意味の警告です。

つまり、new 修飾子は、コンパイラーに対して「これは意図的に隠しています」と伝えるためのものです。

警告を消すためだけに機械的に new を付けるのではなく、本当に隠蔽したいのかを考えることが重要です。

5-5. 隠蔽されたメンバーが呼び出される仕組み

new による隠蔽では、どのメンバーが呼ばれるかは変数の型によって決まります。

次のコードを見てください。

C#
class Parent
{
public void Show()
{
Console.WriteLine("Parent.Show");
}
}

class Child : Parent
{
public new void Show()
{
Console.WriteLine("Child.Show");
}
}

呼び出し側です。

C#
Child child = new Child();
Parent parent = child;

child.Show();
parent.Show();

実行結果は次のようになります。

C#
Child.Show
Parent.Show

child 変数の型は Child なので、Child.Show が呼ばれます。

parent 変数の型は Parent なので、Parent.Show が呼ばれます。

実体が同じ Child インスタンスであっても、new による隠蔽では、参照している変数の型が呼び出し結果に影響します。

この挙動は、後で説明する override とは大きく違います。

5-6. new修飾子を使うべき場面・避けるべき場面

new 修飾子は、使いどころに注意が必要です。

使うべき場面は、基底クラスのメンバーとは別の意味を持つメンバーを、派生クラスで意図的に定義したい場合です。

たとえば、古いAPIとの互換性を保ちながら、新しい意味のメンバーを派生クラスに追加したい場合などです。

一方で、単に基底クラスの動作を変更したいだけなら、new ではなく override を検討するべきです。

new 修飾子を多用すると、変数の型によって呼び出されるメンバーが変わるため、コードを読む人が混乱しやすくなります。

特に初心者のうちは、new 修飾子は「継承元を上書きするもの」ではなく、「隠すもの」だと理解しておきましょう。

6. newとoverrideの違い

C#の継承を学ぶと、newoverride の違いで混乱しやすくなります。

どちらも派生クラスで基底クラスと同じ名前のメソッドを定義するときに使われますが、意味はまったく違います。

new は隠蔽です。

override は上書きです。

この違いを理解すると、継承の挙動がかなり分かりやすくなります。

6-1. newは隠蔽、overrideは上書き

new は、基底クラスのメンバーを隠します。

C#
class Parent
{
public void Show()
{
Console.WriteLine("Parent");
}
}

class Child : Parent
{
public new void Show()
{
Console.WriteLine("Child");
}
}

この場合、変数の型によって呼び出されるメソッドが変わります。

一方、override は、基底クラスの仮想メソッドを派生クラスで上書きします。

C#
class Parent
{
public virtual void Show()
{
Console.WriteLine("Parent");
}
}

class Child : Parent
{
public override void Show()
{
Console.WriteLine("Child");
}
}

override を使うと、変数の型が基底クラスでも、実体が派生クラスなら派生クラスのメソッドが呼ばれます。

6-2. 仮想メソッド・virtual・overrideの関係

override を使うには、基底クラス側のメソッドが virtualabstract、または既に override されている必要があります。MicrosoftのC#リファレンスでも、override は継承された abstract または virtual なメソッド、プロパティ、インデクサー、イベントの実装を拡張または変更するために使うと説明されています。Microsoft Learn

基本形は次のとおりです。

C#
class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal speaks");
}
}

派生クラスで上書きします。

C#
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Dog barks");
}
}

呼び出し側です。

C#
Animal animal = new Dog();
animal.Speak();

実行結果は次のようになります。

C#
Dog barks

変数の型は Animal ですが、実体は Dog なので、DogSpeak が呼ばれます。

これがポリモーフィズムです。

6-3. 変数の型によって呼び出し結果が変わる理由

newoverride の違いは、呼び出し時の決定方法にあります。

new による隠蔽では、変数の型をもとに呼び出し先が決まります。

C#
Parent parent = new Child();
parent.Show();

Shownew で隠蔽されている場合、変数の型が Parent なので Parent.Show が呼ばれます。

一方、override では、実際のインスタンスの型をもとに呼び出し先が決まります。

C#
Parent parent = new Child();
parent.Show();

Showoverride されている場合、実体が Child なので Child.Show が呼ばれます。

この違いが、newoverride のもっとも重要なポイントです。

6-4. newとoverrideの比較コード

まず、new の例です。

C#
class BaseClass
{
public void Show()
{
Console.WriteLine("BaseClass.Show");
}
}

class DerivedClass : BaseClass
{
public new void Show()
{
Console.WriteLine("DerivedClass.Show");
}
}

呼び出します。

C#
DerivedClass derived = new DerivedClass();
BaseClass baseRef = derived;

derived.Show();
baseRef.Show();

実行結果は次のようになります。

C#
DerivedClass.Show
BaseClass.Show

次に、override の例です。

C#
class BaseClass
{
public virtual void Show()
{
Console.WriteLine("BaseClass.Show");
}
}

class DerivedClass : BaseClass
{
public override void Show()
{
Console.WriteLine("DerivedClass.Show");
}
}

呼び出します。

C#
DerivedClass derived = new DerivedClass();
BaseClass baseRef = derived;

derived.Show();
baseRef.Show();

実行結果は次のようになります。

C#
DerivedClass.Show
DerivedClass.Show

new では変数の型によって結果が変わります。

override では実体の型に応じた結果になります。

6-5. 初心者が迷いやすい使い分けの判断基準

初心者が newoverride で迷ったら、次のように考えると分かりやすいです。

基底クラスの動作を派生クラスで置き換えたいなら、基本的には override を使います。

C#
public override void Speak()
{
Console.WriteLine("Dog barks");
}

一方、基底クラスのメンバーとは別物として、同じ名前のメンバーを定義したいなら new を使います。

C#
public new void Speak()
{
Console.WriteLine("New Speak");
}

ただし、実務では new 修飾子を積極的に使う場面は多くありません。継承関係で自然な多態性を表現したい場合は、virtualoverride を使う方が分かりやすいです。

new 修飾子は、既存コードとの互換性や特殊な設計上の理由がある場合に使うものだと考えるとよいでしょう。

7. new()制約とは?ジェネリックでのnewの使い方

C#の new には、ジェネリックで使う new() 制約という意味もあります。

これは、型パラメーターに対して「引数なしコンストラクターを持っている型だけを指定できる」と制限するためのものです。

C#
where T : new()

この制約があると、ジェネリッククラスやジェネリックメソッドの中で new T() が使えるようになります。

7-1. new()制約の意味

ジェネリックでは、型を後から指定できます。

C#
class Box<T>
{
public T Value { get; set; }
}

この T には、intstringPerson など、さまざまな型を指定できます。

しかし、ジェネリックの中で次のように書くことはできません。

C#
class Factory<T>
{
public T Create()
{
return new T(); // エラー
}
}

なぜなら、T が必ず引数なしで生成できる型とは限らないからです。

そこで使うのが new() 制約です。

C#
class Factory<T> where T : new()
{
public T Create()
{
return new T();
}
}

where T : new() を付けることで、T は引数なしコンストラクターを持つ型に限定されます。

7-2. where T : new() の基本構文

new() 制約の基本構文は次のとおりです。

C#
class クラス名<T> where T : new()
{
}

メソッドに付ける場合は次のように書きます。

C#
T Create<T>() where T : new()
{
return new T();
}

具体例です。

C#
class Person
{
public string Name { get; set; }
}

T CreateInstance<T>() where T : new()
{
return new T();
}

Person person = CreateInstance<Person>();

Person には明示的なコンストラクターがありません。この場合、引数なしコンストラクターが使えるため、new() 制約を満たします。

7-3. 引数なしコンストラクターが必要な理由

new T() は、引数なしで T のインスタンスを生成する構文です。

そのため、T に引数なしコンストラクターがなければ実行できません。

たとえば次のクラスを見てください。

C#
class User
{
public string Name { get; }

public User(string name)
{
Name = name;
}
}

このクラスには、User(string name) という引数ありコンストラクターがあります。

しかし、引数なしコンストラクターはありません。

C#
User user = new User(); // エラー

このような型は、where T : new() の型引数として使えません。

C#
class Factory<T> where T : new()
{
public T Create()
{
return new T();
}
}

Factory<User> factory = new Factory<User>(); // エラー

new() 制約を使う場合は、対象の型が引数なしで生成できる必要があります。

7-4. new()制約でインスタンスを生成する例

new() 制約を使った簡単なファクトリークラスを作ってみます。

C#
class Factory<T> where T : new()
{
public T Create()
{
return new T();
}
}

次に、生成対象のクラスを定義します。

C#
class Product
{
public string Name { get; set; } = "未設定";
}

使い方です。

C#
Factory<Product> factory = new Factory<Product>();
Product product = factory.Create();

Console.WriteLine(product.Name);

実行結果は次のようになります。

C#
未設定

Factory<T> の中では、具体的な型が Product であることを知らなくても、new T() でインスタンスを作れます。

ジェネリックメソッドでも同じことができます。

C#
static T Create<T>() where T : new()
{
return new T();
}

呼び出し側です。

C#
Product product = Create<Product>();

7-5. new()制約を使うときの注意点

new() 制約にはいくつか注意点があります。

まず、new() 制約は引数なしコンストラクターしか要求できません。

C#
where T : new()

これは new T() を許可するための制約であり、new T("name") のような引数ありコンストラクターを要求することはできません。

次に、他の制約と組み合わせる場合、new() 制約は最後に書く必要があります。MicrosoftのC#リファレンスでも、new() 制約を他の制約と一緒に使う場合は最後に指定すると説明されています。Microsoft Learn

C#
class Repository<T> where T : class, new()
{
}

次のような順番はエラーになります。

C#
class Repository<T> where T : new(), class
{
}

また、new() 制約を使うと、生成方法が引数なしコンストラクターに固定されます。複雑な初期化が必要な場合は、ファクトリーメソッドやDIコンテナを使う方が柔軟です。

8. newとvar・null・代入の関係

new を正しく理解するには、varnull、代入との関係も押さえておく必要があります。

特に参照型では、「変数にオブジェクトが入っている」と考えるより、「変数はオブジェクトを参照している」と考える方が正確です。

この違いが分かると、NullReferenceException や再代入の挙動も理解しやすくなります。

8-1. var obj = new ClassName() の意味

C#では、ローカル変数の型を var で書くことができます。

C#
var person = new Person();

これは、次のコードとほぼ同じ意味です。

C#
Person person = new Person();

var は「型がない」という意味ではありません。右辺から型を推論しているだけです。

C#
var number = 10;       // int
var text = "Hello"; // string
var person = new Person(); // Person

つまり、var person = new Person(); は、new Person() の結果から、personPerson 型だとコンパイラーが判断しているということです。

ただし、何でも var にすればよいわけではありません。右辺を見ても型が分かりにくい場合は、明示的に型を書いた方が読みやすいこともあります。

8-2. newした変数には何が代入されているのか

クラスを new した場合、変数にはオブジェクトへの参照が入ります。

C#
Person person = new Person();

このコードでは、new Person() によって Person インスタンスが作られ、person 変数はそのインスタンスを参照します。

次のコードを見ると、参照の意味が分かりやすくなります。

C#
Person a = new Person();
a.Name = "佐藤";

Person b = a;
b.Name = "鈴木";

Console.WriteLine(a.Name);

実行結果は次のようになります。

C#
鈴木

b = a によって、a が参照している同じオブジェクトを b も参照するようになります。

そのため、b.Name を変更すると、a.Name から見ても変更後の値になります。

8-3. newしない場合に起きるNullReferenceException

参照型の変数は、new しないままだと null の場合があります。

C#
Person person = null;
person.Name = "佐藤"; // 実行時エラー

このコードは、NullReferenceException になります。

null は「何も参照していない」状態です。何も参照していない変数に対してプロパティやメソッドを使おうとすると、実行時エラーになります。

正しくは、先に new してインスタンスを作ります。

C#
Person person = new Person();
person.Name = "佐藤";

初心者がよく遭遇するエラーの1つが、この NullReferenceException です。

たとえば、クラスの中に別のクラス型のプロパティがある場合にも注意が必要です。

C#
class Order
{
public Customer Customer { get; set; }
}

class Customer
{
public string Name { get; set; }
}

次のコードはエラーになります。

C#
Order order = new Order();
order.Customer.Name = "佐藤"; // エラー

ordernew されていますが、order.Customer はまだ null だからです。

正しくは、Customernew します。

C#
Order order = new Order();
order.Customer = new Customer();
order.Customer.Name = "佐藤";

または、オブジェクト初期化子でまとめて書けます。

C#
Order order = new Order
{
Customer = new Customer
{
Name = "佐藤"
}
};

8-4. 同じ変数にnewし直すとどうなるか

同じ変数に対して、もう一度 new したオブジェクトを代入することもできます。

C#
Person person = new Person();
person.Name = "佐藤";

person = new Person();
person.Name = "鈴木";

Console.WriteLine(person.Name);

実行結果は次のようになります。

C#
鈴木

最初に作った Person インスタンスと、後から作った Person インスタンスは別物です。

C#
person = new Person();

この代入によって、person 変数は新しいインスタンスを参照するようになります。

最初のインスタンスを他の変数が参照していなければ、そのインスタンスは不要になります。

C#
Person person = new Person();
person.Name = "佐藤";

Person oldPerson = person;

person = new Person();
person.Name = "鈴木";

Console.WriteLine(oldPerson.Name);
Console.WriteLine(person.Name);

実行結果は次のようになります。

C#
佐藤
鈴木

oldPerson が最初のインスタンスを参照しているため、最初のオブジェクトもまだ使えます。

8-5. 不要になったインスタンスとガベージコレクション

C#では、不要になったオブジェクトのメモリ管理は主にガベージコレクションが行います。

たとえば次のコードを見てください。

C#
Person person = new Person();
person.Name = "佐藤";

person = new Person();
person.Name = "鈴木";

最初の Person インスタンスを参照する変数がなくなった場合、そのオブジェクトは将来的にガベージコレクションの対象になります。

C#では、CやC++のように手動でメモリを解放する場面は多くありません。

ただし、ファイル、データベース接続、ネットワーク接続など、メモリ以外のリソースを扱う場合は注意が必要です。そのような型では、Disposeusing を使って適切に解放する必要があります。

C#
using FileStream stream = new FileStream("sample.txt", FileMode.Open);

new したものがすべて自動で安全に片付くわけではありません。特に外部リソースを持つオブジェクトでは、リソース解放の考え方も理解しておく必要があります。

9. C#のnewでよくある疑問と間違い

ここでは、C#の new に関して初心者がよく疑問に思うポイントを整理します。

new は基本的なキーワードですが、使える場面と使えない場面を理解していないと、コンパイルエラーや設計上の問題につながります。

9-1. newは毎回必要なのか

クラスの新しいインスタンスを作る場合、基本的には new が必要です。

C#
Person person = new Person();

ただし、すべての変数宣言で new が必要なわけではありません。

たとえば、既に作られたオブジェクトを受け取る場合は new しません。

C#
Person person1 = new Person();
Person person2 = person1;

この場合、person2person1 と同じインスタンスを参照します。

メソッドの戻り値として受け取る場合も、呼び出し側で new しないことがあります。

C#
Person person = GetPerson();

GetPerson メソッドの中でインスタンスが作られているかもしれませんし、既存のインスタンスを返しているかもしれません。

また、文字列リテラルや数値リテラルでは通常 new を書きません。

C#
string message = "Hello";
int number = 10;

new が必要かどうかは、「この場所で新しいインスタンスを作りたいのか」で考えると分かりやすいです。

9-2. staticクラスやstaticメンバーはnewできるのか

static クラスは new できません。

C#
static class MathHelper
{
public static int Add(int a, int b)
{
return a + b;
}
}

次のコードはエラーになります。

C#
MathHelper helper = new MathHelper(); // エラー

static クラスは、インスタンスを作らずに使うためのクラスです。

C#
int result = MathHelper.Add(1, 2);

static メンバーも、インスタンスではなく型名から呼び出します。

C#
class Counter
{
public static int Count { get; set; }
}
C#
Counter.Count = 10;

static は「型そのものに属する」という意味です。インスタンスごとに持つデータではないため、new して使うものではありません。

9-3. interfaceやabstract classはnewできるのか

インターフェイスは直接 new できません。

C#
interface ILogger
{
void Log(string message);
}

次のコードはエラーになります。

C#
ILogger logger = new ILogger(); // エラー

インターフェイスは、実装すべきメンバーの契約を表すものです。実体を持たないため、直接インスタンス化できません。

代わりに、インターフェイスを実装したクラスを new します。

C#
class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
C#
ILogger logger = new ConsoleLogger();
logger.Log("ログ出力");

抽象クラスも直接 new できません。

C#
abstract class Animal
{
public abstract void Speak();
}
C#
Animal animal = new Animal(); // エラー

抽象クラスは未完成のクラスです。実際に使うには、継承した具象クラスを作ります。

C#
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("ワン");
}
}
C#
Animal animal = new Dog();
animal.Speak();

9-4. コンストラクターを省略できる場合はあるのか

クラスにコンストラクターを1つも定義していない場合、C#では引数なしコンストラクターを使えます。

C#
class Person
{
public string Name { get; set; }
}

このクラスは、次のように new できます。

C#
Person person = new Person();

しかし、引数ありコンストラクターを自分で定義すると、引数なしコンストラクターは自動では使えなくなります。

C#
class Person
{
public string Name { get; }

public Person(string name)
{
Name = name;
}
}

この場合、次のコードはエラーになります。

C#
Person person = new Person(); // エラー

正しくは、引数を渡します。

C#
Person person = new Person("佐藤");

引数なしでも生成できるようにしたい場合は、明示的に引数なしコンストラクターを定義します。

C#
class Person
{
public string Name { get; set; }

public Person()
{
Name = "未設定";
}

public Person(string name)
{
Name = name;
}
}

9-5. newを使いすぎると何が問題になるのか

new は便利ですが、どこでも直接使いすぎると、コードの柔軟性が下がることがあります。

たとえば、あるクラスの中で具体的なクラスを直接 new しているとします。

C#
class OrderService
{
private readonly EmailSender _emailSender = new EmailSender();

public void CompleteOrder()
{
_emailSender.Send();
}
}

このコードでは、OrderServiceEmailSender に強く依存しています。

将来、メール送信ではなくSMS送信に変えたい場合や、テスト用の送信クラスに差し替えたい場合に変更しづらくなります。

このような場合は、インターフェイスを使って依存を外から渡す設計にすると柔軟になります。

C#
interface IMessageSender
{
void Send();
}

class EmailSender : IMessageSender
{
public void Send()
{
Console.WriteLine("メール送信");
}
}

class OrderService
{
private readonly IMessageSender _messageSender;

public OrderService(IMessageSender messageSender)
{
_messageSender = messageSender;
}

public void CompleteOrder()
{
_messageSender.Send();
}
}

このように、new をどこに書くかは設計に影響します。

小さなプログラムでは直接 new して問題ありません。しかし、規模が大きくなると、依存関係を整理するために new の場所を工夫することが重要になります。

9-6. DIやファクトリーパターンではnewをどう扱うのか

DIはDependency Injectionの略で、日本語では依存性注入と呼ばれます。

DIでは、クラスの中で必要なオブジェクトを直接 new するのではなく、外から渡します。

C#
class ReportService
{
private readonly IReportWriter _writer;

public ReportService(IReportWriter writer)
{
_writer = writer;
}

public void Output()
{
_writer.Write();
}
}

このようにすると、ReportService は具体的な実装クラスを知らなくて済みます。

C#
IReportWriter writer = new CsvReportWriter();
ReportService service = new ReportService(writer);

実際のアプリケーションでは、DIコンテナがインスタンス生成を担当することもあります。

ファクトリーパターンでは、インスタンス生成の処理を専用のクラスやメソッドにまとめます。

C#
class UserFactory
{
public User Create(string name)
{
return new User(name);
}
}

呼び出し側です。

C#
UserFactory factory = new UserFactory();
User user = factory.Create("佐藤");

このようにすると、生成処理を1か所にまとめられます。

つまり、DIやファクトリーパターンでは、new をなくすのではなく、new を書く場所を整理します。

10. C#のnewをコード例で総復習

ここまで学んだ new の使い方を、コード例でまとめて復習します。

インスタンス生成、オブジェクト初期化子、配列、コレクション、new 修飾子、overridenew() 制約を一気に確認しましょう。

10-1. インスタンス生成の基本例

まずは、クラスのインスタンス生成です。

C#
class Person
{
public string Name { get; set; }

public void Greet()
{
Console.WriteLine($"こんにちは、{Name}です");
}
}
C#
Person person = new Person();
person.Name = "佐藤";
person.Greet();

実行結果です。

C#
こんにちは、佐藤です

引数ありコンストラクターの例です。

C#
class Person
{
public string Name { get; }

public Person(string name)
{
Name = name;
}

public void Greet()
{
Console.WriteLine($"こんにちは、{Name}です");
}
}
C#
Person person = new Person("鈴木");
person.Greet();

実行結果です。

C#
こんにちは、鈴木です

10-2. オブジェクト初期化子の例

オブジェクト初期化子を使うと、生成と同時にプロパティを設定できます。

C#
class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
C#
Product product = new Product
{
Name = "ノートPC",
Price = 120000
};

Console.WriteLine(product.Name);
Console.WriteLine(product.Price);

実行結果です。

C#
ノートPC
120000

init プロパティを使う例です。

C#
class User
{
public string Name { get; init; }
public int Age { get; init; }
}
C#
User user = new User
{
Name = "田中",
Age = 30
};

生成後に変更しようとするとエラーになります。

C#
user.Name = "佐藤"; // エラー

10-3. 配列・コレクション初期化の例

配列を new で作る例です。

C#
int[] numbers = new int[3];

numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;

初期値を同時に指定する例です。

C#
int[] numbers = new int[] { 10, 20, 30 };

リストを初期化する例です。

C#
List<string> names = new List<string>
{
"佐藤",
"鈴木",
"田中"
};

オブジェクトのリストを初期化する例です。

C#
List<Person> people = new List<Person>
{
new Person { Name = "佐藤" },
new Person { Name = "鈴木" },
new Person { Name = "田中" }
};

target-typed newを使うと、次のようにも書けます。

C#
List<Person> people = new()
{
new Person { Name = "佐藤" },
new Person { Name = "鈴木" }
};

10-4. new修飾子による隠蔽の例

new 修飾子による隠蔽の例です。

C#
class Parent
{
public void Show()
{
Console.WriteLine("Parent.Show");
}
}

class Child : Parent
{
public new void Show()
{
Console.WriteLine("Child.Show");
}
}

呼び出します。

C#
Child child = new Child();
Parent parent = child;

child.Show();
parent.Show();

実行結果です。

C#
Child.Show
Parent.Show

new による隠蔽では、変数の型によって呼び出されるメソッドが変わります。

10-5. overrideとの違いが分かる例

次に、override の例です。

C#
class Parent
{
public virtual void Show()
{
Console.WriteLine("Parent.Show");
}
}

class Child : Parent
{
public override void Show()
{
Console.WriteLine("Child.Show");
}
}

呼び出します。

C#
Child child = new Child();
Parent parent = child;

child.Show();
parent.Show();

実行結果です。

C#
Child.Show
Child.Show

override では、変数の型が Parent でも、実体が Child なら Child.Show が呼ばれます。

new は隠蔽、override は上書きです。

10-6. ジェネリックのnew()制約の例

最後に、new() 制約の例です。

C#
class Factory<T> where T : new()
{
public T Create()
{
return new T();
}
}

生成対象のクラスです。

C#
class Product
{
public string Name { get; set; } = "未設定";
}

使い方です。

C#
Factory<Product> factory = new Factory<Product>();
Product product = factory.Create();

Console.WriteLine(product.Name);

実行結果です。

C#
未設定

where T : new() を付けることで、ジェネリック型 T に対して new T() を使えるようになります。

まとめ

C#の new は、初心者にとって最初は少し分かりにくいキーワードです。なぜなら、同じ new でも使われる場所によって意味が変わるからです。

もっとも基本的な使い方は、インスタンス生成です。

C#
Person person = new Person();

これは、Person クラスから新しいオブジェクトを作るコードです。

オブジェクト初期化子を使うと、生成と同時にプロパティを設定できます。

C#
Person person = new Person
{
Name = "佐藤",
Age = 25
};

配列やコレクションでも new はよく使われます。

C#
int[] numbers = new int[] { 1, 2, 3 };

List<string> names = new List<string>
{
"佐藤",
"鈴木"
};

また、C#には new 修飾子もあります。

C#
public new void Show()
{
Console.WriteLine("Child");
}

これは、継承元のメンバーを上書きするものではなく、隠蔽するものです。

基底クラスの動作を派生クラスで置き換えたい場合は、通常 override を使います。

C#
public override void Show()
{
Console.WriteLine("Child");
}

さらに、ジェネリックでは new() 制約を使うことで、型パラメーターに引数なしコンストラクターを要求できます。

C#
class Factory<T> where T : new()
{
public T Create()
{
return new T();
}
}

C#の new を理解するコツは、「どこで使われている new なのか」を見ることです。

new ClassName() の形ならインスタンス生成、メンバー宣言の前にある new なら隠蔽、where T : new() ならジェネリック制約です。

この3つを区別できるようになると、C#のコードがかなり読みやすくなります。