C#におけるnewキーワードは、言語の基幹を成す非常に多機能な要素です。
オブジェクトのインスタンス化を担う「演算子」としての役割だけでなく、継承関係におけるメンバの隠蔽を定義する「修飾子」、さらにはジェネリクスにおける「型制約」など、その用途は多岐にわたります。
近年、C#は急速な進化を遂げており、C# 9.0で導入されたターゲット型new (Target-typed new expressions)や、最新バージョンにおけるコレクション式との兼ね合いなど、記述をより簡潔にするための機能が拡充されています。
本記事では、初学者から実務経験者までを対象に、C#のnewにまつわる機能を網羅し、現場で役立つ具体的なコーディングテクニックと注意点を徹底的に解説します。
1. 演算子としてのnew:インスタンスの生成
C#において最も頻繁に利用されるのが、インスタンスを生成するためのnew演算子です。
クラス(参照型)や構造体(値型)の新しいインスタンスを作成し、コンストラクタを呼び出す際に使用します。
基本的な使い方
参照型の場合、new演算子はマネージドヒープ上にメモリを割り当て、オブジェクトを初期化します。
一方、値型の場合は、宣言された場所(スタックまたは格納先のオブジェクト内)で初期化が行われます。
using System;
public class SampleClass
{
public string Name { get; set; }
// コンストラクタ
public SampleClass(string name)
{
Name = name;
Console.WriteLine($"{Name} が生成されました。");
}
}
public class Program
{
public static void Main()
{
// new演算子によるインスタンス化
SampleClass sc = new SampleClass("テストインスタンス");
// 構造体(値型)の場合
DateTime now = new DateTime(2026, 2, 21);
Console.WriteLine($"日付: {now.ToShortDateString()}");
}
}
テストインスタンス が生成されました。
日付: 2026/02/21
オブジェクト初期化子の活用
new演算子によるインスタンス化と同時に、プロパティやフィールドに値をセットできるオブジェクト初期化子も重要な機能です。
これにより、引数付きコンストラクタをわざわざ定義しなくても、柔軟な初期化が可能になります。
var person = new Person
{
FirstName = "太郎",
LastName = "田中",
Age = 30
};
このように記述することで、コードの可読性が大幅に向上し、どの値がどのプロパティに設定されているかが一目でわかるようになります。
2. ターゲット型new (Target-typed new) の進化
C# 9.0以降、型の推論が可能な文脈において、型名の記述を省略できるターゲット型newが導入されました。
これにより、特にジェネリクスを多用する複雑な型宣言において、冗長な記述を排除できるようになりました。
型名の省略による簡潔な記述
これまでは、左辺と右辺の両方に型名を記述するか、varを使用する必要がありました。
ターゲット型newを使えば、右辺を単にnew()と書くだけで済みます。
using System.Collections.Generic;
public class TargetTypeExample
{
// フィールド宣言での利用
private Dictionary<int, List<string>> _cache = new();
public void Execute()
{
// 従来の書き方
List<string> list1 = new List<string>();
// varを用いた書き方
var list2 = new List<string>();
// ターゲット型new(C# 9.0〜)
List<string> list3 = new();
// メソッドの引数としても利用可能
ProcessData(new());
}
public void ProcessData(List<string> data)
{
// 処理内容
}
}
ターゲット型newの使いどころと注意点
ターゲット型newは非常に便利ですが、varと同時に使用することはできません。
以下のコードはコンパイルエラーとなります。
// エラー:型が推論できないため
var myObject = new();
「型名が自明であるかどうか」を基準に使い分けるのがベストプラクティスです。
例えば、フィールド宣言や引数リストではターゲット型newが非常に有効ですが、ローカル変数ではvarを使った方が直感的な場合もあります。
3. 修飾子としてのnew:メンバの隠蔽 (Shadowing)
継承関係において、基底クラス(親クラス)で定義されているメンバと同じ名前のメンバを派生クラス(子クラス)で定義する場合、new修飾子を使用します。
これは「オーバーライド」とは本質的に異なる概念です。
オーバーライド (override) と隠蔽 (new) の違い
overrideは多態性(ポリモーフィズム)を実現し、変数の型に関わらず「インスタンス本来の型」のメソッドを呼び出します。
一方、new修飾子は、単に基底クラスのメンバを「隠す」だけであり、参照している変数の型によって呼び出されるメソッドが決定されます。
using System;
public class BaseClass
{
public void Display() => Console.WriteLine("Baseのメソッド");
}
public class DerivedClass : BaseClass
{
// new修飾子による隠蔽
public new void Display() => Console.WriteLine("Derivedのメソッド");
}
public class Program
{
public static void Main()
{
DerivedClass d = new DerivedClass();
d.Display(); // Derivedのメソッドが呼ばれる
BaseClass b = d;
b.Display(); // Baseのメソッドが呼ばれる(ここがoverrideとの違い)
}
}
Derivedのメソッド
Baseのメソッド
なぜnew修飾子が必要なのか
一般的に、意図的な隠蔽は避けるべき設計とされますが、外部ライブラリの更新などで基底クラスに新しいメソッドが追加され、自クラスの既存メソッドと名前が衝突してしまった場合などに、「意図的に隠蔽していること」を明示して警告を消すために使用されます。
4. ジェネリクスにおけるnew()制約
ジェネリクス(汎用型)を使用する際、型パラメータ T のインスタンスをメソッド内部で生成したい場合があります。
このとき必要になるのがnew()制約です。
型制約の役割
デフォルトでは、ジェネリクスの型パラメータがコンストラクタを持っているかどうかをコンパイラは知りません。
そのため、where T : new() と記述することで、「引数なしのパブリックなコンストラクタを持つ型であること」を保証させます。
public class Factory<T> where T : new()
{
public T CreateInstance()
{
// new()制約があるため、Tのインスタンス化が可能
return new T();
}
}
public class MyService
{
public void Run()
{
var factory = new Factory<SampleData>();
var data = factory.CreateInstance();
}
}
public class SampleData
{
public SampleData() { } // デフォルトコンストラクタが必要
}
制約の組み合わせ
new()制約は他の制約と組み合わせることも可能ですが、記述する順番にはルールがあります。
必ず最後に記述しなければなりません。
// 正しい例:クラス制約の後にnew()制約
public class Repository<T> where T : class, new()
{
// ...
}
これにより、データベースのリポジトリパターンや依存注入(DI)コンテナの簡易実装など、汎用的なオブジェクト生成ロジックを安全に記述できるようになります。
5. 匿名型とnew演算子
LINQクエリなどで一時的なデータのまとまりを扱いたい場合、クラスを定義せずにその場で型を作成する匿名型 (Anonymous Types)が利用されます。
ここでもnewキーワードが活躍します。
匿名型の構文
匿名型は、読み取り専用のプロパティを持つ型として定義されます。
using System;
using System.Linq;
public class AnonymousExample
{
public static void Main()
{
var products = new[]
{
new { Name = "Apple", Price = 100 },
new { Name = "Banana", Price = 150 },
new { Name = "Orange", Price = 120 }
};
// LINQで匿名型を生成
var cheapProducts = products
.Where(p => p.Price < 130)
.Select(p => new { p.Name, TaxIncluded = p.Price * 1.1 });
foreach (var item in cheapProducts)
{
Console.WriteLine($"{item.Name}: {item.TaxIncluded}円");
}
}
}
Apple: 110円
Orange: 132円
匿名型は、「その場限りのデータ転送」には非常に強力ですが、メソッドの戻り値として直接返すことは困難(object型やdynamic型にする必要がある)なため、スコープが広い場合はrecordやstructの使用を検討すべきです。
6. 配列とコレクションの初期化におけるnew
C#では配列の生成にもnewを使用します。
古くからある配列生成の記述から、最新のコレクション式まで、その変遷を整理しておきましょう。
配列の生成
配列は参照型であるため、生成にはnew演算子が必要です。
// 明示的な型指定
int[] numbers1 = new int[3] { 1, 2, 3 };
// 型推論を利用した配列生成
var numbers2 = new[] { 1, 2, 3 };
// C# 12以降のコレクション式(newさえ省略可能に)
int[] numbers3 = [1, 2, 3];
現代的なC#開発においては、C# 12で導入されたコレクション式 []の使用が推奨されますが、古いコードベースの保守や、特定のサイズで初期化された空の配列を確保する場合には、依然としてnew int[size]のような記述が欠かせません。
7. パフォーマンスとメモリ管理:newの内部挙動
C#のプログラマとしてレベルアップするためには、newが実行時にどのようなコストを発生させるかを理解しておく必要があります。
ヒープ割り当てのコスト
クラスに対してnewを呼び出すと、マネージドヒープ上にメモリが確保されます。
このメモリは最終的にガベージコレクション(GC)によって回収されますが、頻繁なnew(特にループ内での短寿命オブジェクトの生成)はGCの負荷を高め、アプリケーションのパフォーマンス低下(GCポーズ)を招く原因となります。
回避策としての構造体とstackalloc
高いパフォーマンスが要求される場面では、newによるヒープ割り当てを避ける手法が取られます。
- 構造体 (struct): スタック上に配置されるため、GCの対象になりません。
- stackalloc: スタック上にメモリを直接割り当てる機能です。
// スタック上にメモリを確保(new演算子とは異なるメモリ管理)
Span<int> buffer = stackalloc int[100];
「何でもかんでもnewでクラスを生成する」のではなく、データの寿命やサイズに応じて適切な型を選択することが、堅牢なシステム構築の鍵となります。
8. 実践:newを使いこなす設計パターン
最後に、newに関連する機能を組み合わせた実践的なテクニックを紹介します。
ファクトリメソッドとnew()制約の応用
プラグインアーキテクチャや、動的にコンポーネントを生成するシステムでは、ジェネリクス制約を活用したインスタンス生成がよく使われます。
public interface IPlugin
{
void Initialize();
}
public class PluginManager<T> where T : IPlugin, new()
{
public T LoadPlugin()
{
var plugin = new T();
plugin.Initialize();
return plugin;
}
}
このパターンを使用すると、呼び出し側は特定の型を意識することなく、new()制約のおかげで安全に初期化済みのオブジェクトを受け取ることができます。
record型とターゲット型newの相性
C# 9.0で導入されたrecordは、不変(Immutable)なデータ構造を定義するのに適しています。
ターゲット型newと組み合わせることで、データ定義のコードが非常に美しくなります。
public record User(int Id, string Name);
public class UserService
{
private readonly List<User> _users = new(); // ターゲット型new
public void AddUser(int id, string name)
{
_users.Add(new(id, name)); // ターゲット型newによる簡潔な追加
}
}
このように、新しい言語機能を組み合わせることで、「意図が明確で、かつノイズの少ないコード」を実現できます。
9. まとめ
C#のnewは、単純なオブジェクト生成の枠を超え、言語の柔軟性と安全性を支える重要なキーワードです。
- new演算子:インスタンス化の基本。オブジェクト初期化子と併用して読みやすく。
- ターゲット型new:C# 9.0からの新機能。型名が明らかな場合に記述を省略し、コードをクリーンに保つ。
- new修飾子:継承におけるメンバ隠蔽。意図しない隠蔽を避け、必要な時だけ明示的に使用。
- new()制約:ジェネリクスでインスタンス生成を可能にする必須テクニック。
- 匿名型と配列:一時的なデータ処理やコレクション操作の基盤。
これらの機能を適切に使い分けることで、パフォーマンスと可読性のバランスが取れた高度なプログラムを書くことが可能になります。
特にターゲット型newや最新のコレクション式は、今後のC#開発において標準的なスタイルとなっていくため、積極的に取り入れていきましょう。
C#という言語は、後方互換性を保ちながらも、よりモダンで簡潔な記述ができるよう進化し続けています。
newという一つのキーワードに注目するだけでも、その進化の歴史と、開発者の負担を減らそうとする設計思想が見えてくるはずです。
本記事で解説した内容を参考に、日々のコーディングをより効率的で楽しいものにしてください。






