【C#】target-typed new完全ガイド|var vs new()・使えるコンテキスト・コレクション式・.editorconfig設定まで

【C#】target-typed new 式でコードを簡潔に書く方法 C#

C# 9 で導入された target-typed new は、左辺の型情報から右辺の new の型を推論する機能です。Dictionary<string, List<int>> map = new(); のように長い型名の繰り返しを排除でき、C# 10 の global usingfile-scoped namespace と合わせてファイル冒頭のボイラープレートを大幅に削減するモダン C# の要です。

本記事では target-typed new が使える全コンテキストを列挙し、var 派 vs new() 派の使い分け、target-typed conditional / null coalescing・コレクション式(C# 12)との関係・.editorconfig によるチームスタイル統一・制限事項と落とし穴まで体系的に解説します。

スポンサーリンク

Before / After — 何が変わったか

C# 8 以前と C# 9 以降の比較
// Before(C# 8 以前): 左辺と右辺に同じ型を書く冗長なコード
Dictionary<string, List<int>> scores = new Dictionary<string, List<int>>();
StringBuilder sb = new StringBuilder(256);
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

// After(C# 9+): 左辺で型が分かるので右辺は new() だけ
Dictionary<string, List<int>> scores = new();
StringBuilder sb = new(256);
CancellationTokenSource cts = new(TimeSpan.FromSeconds(30));

// コンストラクタ引数・初期化子もそのまま使える
Dictionary<string, int> map = new()
{
    ["Alice"] = 90,
    ["Bob"]   = 85,
};

List<string> names = new(capacity: 100) { "A", "B", "C" };
IL(中間言語)は完全に同一
new Dictionary<string, int>()new() はコンパイル後のILが同じです。パフォーマンスに一切影響はなく、純粋にソースコードの可読性・記述量の改善です。

var vs 明示型 + new() — どちらを使うか

書き方 左辺 右辺 型情報がどこに
var x = new List<string>(); 推論 明示 右辺に書く
List<string> x = new(); 明示 推論 左辺に書く
List<string> x = new List<string>(); 明示 明示 両方(冗長)
var x = new(); 推論 推論 どこにもない(NG
var 派と new() 派の比較
// var 派: 「右辺で型が分かるなら左辺は var」
var list   = new List<string>();        // 右辺に型名がある
var user   = new User("Alice", 30);    // 右辺に型名がある
var scores = new Dictionary<string, int>();

// new() 派: 「左辺で型が分かるなら右辺は new()」
List<string> list2              = new();
User user2                      = new("Alice", 30);
Dictionary<string, int> scores2 = new();

// 両方が使えるとき、チームでどちらかに統一するのがベスト
// → .editorconfig で制御(後述)

// 型が「どちらにも」書かれない場合はコンパイルエラー
// var x = new();  // CS8754: 型が推論できません
「左辺で型を読みたい」か「右辺で型を読みたい」か
var 派は「右辺の new Xxx(...) を見て型を知る」スタイルで、new() 派は「左辺の型宣言を見て型を知る」スタイルです。どちらが読みやすいかは個人・チームの好みですが、1つのプロジェクト内で混在させないことが最も重要です。.editorconfig でルールを設定し、IDE の警告で統一してください。

target-typed new が使える全コンテキスト

① 変数宣言
List<int> numbers = new();
StringBuilder sb  = new(128);
② フィールド・プロパティの初期化
public class OrderService
{
    // フィールド初期化
    private readonly List<Order> _orders = new();
    private readonly SemaphoreSlim _gate = new(10);

    // プロパティ初期化(init / set どちらでも)
    public List<string> Tags { get; init; } = new();
    public ConcurrentDictionary<string, int> Cache { get; } = new();
}
③ メソッド引数
// 引数の型が確定しているなら new() で渡せる
public void Configure(JsonSerializerOptions options) { }

Configure(new() { WriteIndented = true });  // 型推論で JsonSerializerOptions

// コレクション引数
public void AddRange(List<int> items) { }
AddRange(new() { 1, 2, 3 });
④ return 文
public List<User> GetDefaultUsers()
{
    return new()   // List<User> と推論
    {
        new("Alice", 30),
        new("Bob", 25),
    };
}

// 式形式
public List<int> Empty() => new();
⑤ throw 式
// 例外の new にも使える
public string Name { get; init; } = ""
    ?? throw new();  // NullReferenceException() ← 型が文脈から推論されない場合は使えない

// 一般的には例外は型名を明示する方が読みやすいのでこの用途は稀
public string Name2 => _name ?? throw new InvalidOperationException("Name is required");
⑥ 三項演算子(target-typed conditional, C# 9+)
// C# 9+ では三項演算子の結果型をターゲット型から推論できる
ILogger logger = useConsole
    ? new ConsoleLogger()     // ILogger に暗黙変換可能
    : new FileLogger("app");  // ILogger に暗黙変換可能

// C# 8 以前ではキャストが必要だった
ILogger logger2 = useConsole
    ? (ILogger)new ConsoleLogger()
    : (ILogger)new FileLogger("app");

// new() と組み合わせることもできる(左辺の型が確定していれば)
List<string> items = condition ? new() { "a" } : new() { "b", "c" };
⑦ null 合体演算子(?? / ??=)
// ?? の右辺で new()
List<string> tags = existingTags ?? new();

// ??= の右辺で new()(遅延初期化パターン)
private List<Order>? _orders;
public List<Order> Orders => _orders ??= new();

// init 付きプロパティのデフォルト値として
public HashSet<string> AllowedRoles { get; init; } = new() { "admin", "user" };
⑧ 配列の要素初期化
// 配列型が確定していれば各要素に new()
Point[] triangle = { new(0, 0), new(1, 0), new(0, 1) };

// ジェネリックコレクション内
List<Point> points = new() { new(0, 0), new(1, 0), new(0, 1) };
⑨ switch 式の各アーム
IShape shape = type switch
{
    "circle"    => new Circle(radius: 5),
    "rectangle" => new Rectangle(10, 20),
    _           => new UnknownShape(),
};
⑩ LINQ のラムダ内
// Select の戻り値型が推論されるケース
IEnumerable<User> users = names.Select(n => new User(n, 0));
// → Select<string, User> で User が推論されるため new User(...) は必須

// ToList の型指定あり
List<Point> points = coords.Select(c => new Point(c.X, c.Y)).ToList();

コレクション式(C# 12)との関係

C# 12 で導入されたコレクション式 [...] はtarget-typed new の「コレクション特化版」とも言える機能で、List<T>T[]Span<T>ImmutableArray<T> など幅広い型に対して統一された初期化構文を提供します。

コレクション式の基本(C# 12+)
// Before(C# 9 target-typed new)
List<int> a = new() { 1, 2, 3 };
int[] b     = new[] { 1, 2, 3 };
Span<int> c = stackalloc int[] { 1, 2, 3 };

// After(C# 12 コレクション式)
List<int> a2 = [1, 2, 3];
int[] b2     = [1, 2, 3];
Span<int> c2 = [1, 2, 3];

// 空コレクション
List<string> empty = [];   // new() の代わりに []

// スプレッド演算子で結合
int[] first = [1, 2, 3];
int[] second = [4, 5, 6];
int[] combined = [.. first, .. second]; // [1, 2, 3, 4, 5, 6]

// ImmutableArray にも使える
ImmutableArray<string> names = ["Alice", "Bob"];

// Dictionary はコレクション式の対象外(C# 12 時点)
// → 引き続き new() { ["key"] = value } を使う
Dictionary<string, int> scores = new() { ["Alice"] = 90 };
コレクション型 new() 構文 [...] 構文(C# 12+)
List<T> new() { 1, 2, 3 } [1, 2, 3]
T[] new[] { 1, 2, 3 } [1, 2, 3]
Span<T> stackalloc T[] { ... } [1, 2, 3]
ImmutableArray<T> ImmutableArray.Create(1, 2, 3) [1, 2, 3]
HashSet<T> new() { 1, 2, 3 } [1, 2, 3]
空コレクション new() []
Dictionary<K,V> new() { [k] = v } 非対応
C# 12 ではコレクションは []、それ以外は new()
コレクション式 [...] が使える型にはそちらを使い、それ以外のオブジェクト生成には new() を使うのがC# 12 以降のスタイルです。特に空リスト・空配列[] が最も簡潔で、コンパイラが Array.Empty<T>() への最適化を行うケースもあります。

.editorconfig — チームスタイルの統一

.editorconfig で new() スタイルを強制
// .editorconfig に以下を設定するとIDEが警告/エラーを出す

// 「型が明らかなときは new() を使え」(new() 派向け)
// csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion

// IDE0090: 'new' 式を簡略化できます
// → Dictionary<string, int> scores = new Dictionary<string, int>();
//    と書くと IDE が new() への変更を提案する

// ルールの厳格度:
// :silent     → 非表示(自動修正のみ)
// :suggestion → メッセージとして表示
// :warning    → 警告(黄色の波線)
// :error      → エラー(ビルドを止める)

// 推奨設定(.editorconfig の [*.cs] セクション):
// csharp_style_implicit_object_creation_when_type_is_apparent = true:warning

// var 派 vs new() 派の両方をカバーする設定例:
// csharp_style_var_for_built_in_types = true:suggestion
// csharp_style_var_when_type_is_apparent = true:suggestion
// csharp_style_var_elsewhere = false:suggestion
// csharp_style_implicit_object_creation_when_type_is_apparent = true:warning

struct / record struct での target-typed new

new() と default の違い
// 値型では new() と default の動作が異なる場合がある

// パラメータなしコンストラクタがある struct(C# 10+)
public struct Counter
{
    public int Value { get; set; }
    public Counter() => Value = 1;  // カスタムパラメータなしコンストラクタ
}

Counter a = new();      // Value = 1(コンストラクタが呼ばれる)
Counter b = default;    // Value = 0(全フィールドゼロ初期化、コンストラクタは呼ばれない)
Counter c = new Counter(); // Value = 1(new() と同じ)

// readonly record struct
public readonly record struct Point(int X, int Y);
Point p = new(3, 4);  // target-typed new で生成

// 重要: default と new() は struct では異なる結果になり得る
// default は常にゼロ初期化、new() はコンストラクタを呼ぶ

制限事項

target-typed new が使えないケース
// ① var と組み合わせ不可(型情報がどこにもない)
// var x = new();  // CS8754

// ② dynamic 型への推論不可
// dynamic d = new();  // CS8754

// ③ オーバーロード解決が曖昧な場合
public void Process(List<int> items) { }
public void Process(HashSet<int> items) { }
// Process(new() { 1, 2, 3 });  // CS0121: オーバーロードが曖昧

// ④ 匿名型は new() で作れない(匿名型にはコンストラクタがない)
// var anon = new { Name = "Alice" };  // これは従来の匿名型(new() ではない)

// ⑤ インターフェース型への直接 new()
// IList<int> x = new();  // CS8754: IList は new() で生成できない
// IList<int> x = new List<int>(); // OK: 具象型を明示

// ⑥ Nullable<T> への new()
// int? x = new();  // これは int? の new() = 0 になる(意図と違う可能性)
int? x = new();
Console.WriteLine(x);        // 0(null ではない)
Console.WriteLine(x is null); // false

// ⑦ ジェネリック型パラメータ T への new()
public T Create<T>() where T : new()
{
    return new T();  // OK(new 制約があるため。明示的に型を書く方が読みやすい)
    // return new(); // C# 9+ で動作する場合もあるが、T が具象型でないため可読性が低い
}

実践パターン

パターン① — DI 登録での Options 初期化
builder.Services.Configure<SmtpOptions>(opt =>
{
    opt.Host = "smtp.example.com";
    opt.Port = 587;
});

// PostConfigure のデフォルト値設定
builder.Services.PostConfigure<SmtpOptions>(opt =>
{
    opt.AllowedRecipients ??= new();  // null なら空リストを設定
});
パターン② — テストの Arrange で簡潔に
[Fact]
public async Task Order_Succeeds()
{
    // Arrange: 長い型名を new() で省略
    var svc = new OrderService(
        new FakeOrderRepository(),
        new FakePaymentGateway(),
        NullLogger<OrderService>.Instance);

    // フィールド初期化も new() で
    Order order = new()
    {
        Id         = 1,
        CustomerId = 42,
        Items      = new() { new("Book", 1500), new("Pen", 200) },
        CreatedAt  = DateTime.UtcNow,
    };

    // Act
    var result = await svc.ProcessAsync(order);

    // Assert
    Assert.True(result.Success);
}
パターン③ — パターンマッチングとの組み合わせ
// switch 式で異なる型のインスタンスを返す
public INotification CreateNotification(string type, string message)
    => type switch
    {
        "email" => new EmailNotification { To = "a@x", Body = message },
        "slack" => new SlackNotification { Channel = "#dev", Text = message },
        "sms"   => new SmsNotification   { Phone = "090...", Body = message },
        _       => throw new ArgumentException($"Unknown type: {type}"),
    };
パターン④ — 遅延初期化 (??=) と組み合わせ
public class LazyCache
{
    private Dictionary<string, object>? _store;

    // 最初のアクセスで初期化
    public Dictionary<string, object> Store => _store ??= new();
}

// init 付きでもデフォルト値として
public class Config
{
    public required string Name { get; init; }
    public List<string> Tags { get; init; } = new();
    public Dictionary<string, string> Metadata { get; init; } = new();
}

よくある落とし穴

落とし穴① — new() で型が分かりにくくなるケース
// NG: ジェネリックメソッドチェーンの深い位置で new() を使うと読みづらい
var result = Process(new(), new(), new());  // 引数の型がシグネチャを見ないと分からない

// OK: 引数が多いときは変数に分ける or 名前付き引数を使う
HttpClientHandler handler = new() { AllowAutoRedirect = false };
var result2 = Process(handler, new CancellationTokenSource(TimeSpan.FromSeconds(30)));
落とし穴② — Nullable への new() が null ではない
// int? x = new(); は 0 を返す(null ではない!)
int? x = new();
Console.WriteLine(x.HasValue); // true
Console.WriteLine(x.Value);    // 0

// null にしたいなら
int? y = null;
int? z = default; // これも null(Nullable<int> の default は null)

// new() が default と異なる動作をするのは struct の場合
// 参照型では new() は常にインスタンス生成、default は null
落とし穴③ — メソッドグループの解決が変わる
// オーバーロードがある場合、new() は解決ができないことがある
public static void Print(StringBuilder sb) => Console.WriteLine(sb);
public static void Print(StringWriter sw) => Console.WriteLine(sw);

// Print(new());  // CS0121: 曖昧
Print(new StringBuilder());  // OK: 型を明示

// → new() はオーバーロード解決に型情報を提供しないため、
//    呼び出し先が一意に決まらないと曖昧エラーになる

よくある質問

Qvar と new() はどちらを使うべきですか?
A結論はチームで統一することです。「右辺に型名がある場合は var」「左辺に型名がある場合は new()」というのが基本で、片方だけ見て型が分かるように書きます。.editorconfigcsharp_style_implicit_object_creation_when_type_is_apparent = true:warning を設定するのが推奨です。個人的な好みでルールを緩くするより、IDE が自動修正してくれる状態にしておくと PR レビューのノイズが減ります。
Qtarget-typed new はパフォーマンスに影響しますか?
A一切影響しません。コンパイル後の IL は new T() と完全に同一です。target-typed new は「型推論をコンパイラが行う」だけのシンタックスシュガーであり、ランタイムの実行パスは何も変わりません。
Qコレクション式 […] と new() { … } はどちらを優先すべき?
AC# 12 以降ならコレクション式 [...] を優先してください。List<int> x = [1, 2, 3];new() { 1, 2, 3 } より短く、Span<T>ImmutableArray<T> にも統一的に使えます。空コレクションの []Array.Empty<T>() への最適化が行われるケースもあり、パフォーマンス上も有利です。ただし Dictionary はコレクション式非対応なので new() { ["k"] = v } を引き続き使います。
Qtarget-typed new は C# 9 でないと使えませんか?
Atarget-typed newC# 9 の言語機能です。ターゲットフレームワーク(.NET 5 / .NET 6 等)ではなくコンパイラの言語バージョンで決まるため、<LangVersion>9</LangVersion> 以上が必要です。.NET 5+ なら既定で C# 9 以上が使えます。.NET Core 3.1 や .NET Framework でも <LangVersion>latest</LangVersion> を .csproj に設定すれば使える場合がありますが、ランタイムバージョンとの組み合わせに制約があるため注意が必要です。
Qstruct のカスタムパラメータなしコンストラクタは C# 何からですか?
AC# 10(.NET 6+)からです。C# 9 以前は struct にパラメータなしコンストラクタを定義できませんでした。C# 10 以降は public struct Counter { public Counter() => Value = 1; } と書けますが、default 式はコンストラクタを呼ばず全フィールドをゼロ初期化するため、new()default で挙動が異なる点に注意が必要です。

まとめ

項目 ベストプラクティス
基本 左辺に型を書いたら右辺は new()。型を2回書かない
var との棲み分け var は右辺に型名があるとき、new() は左辺に型があるとき
使えるコンテキスト 変数・フィールド・プロパティ・引数・return・三項演算子・null合体・switch式
コレクション式 C# 12+ なら [1, 2, 3] を優先。Dictionary は new()
struct + new() C# 10+ のカスタムコンストラクタでは new()default の挙動が異なる
チーム統一 .editorconfig の IDE0090 で :warning 設定
制限 var との組み合わせ不可・オーバーロード曖昧・インターフェース型不可
Nullable<T> int? x = new() は null ではなく 0 になる点に注意

関連するモダン C# 機能は以下を参照してください。global using 完全ガイドで using の冗長性削減、パターンマッチング完全ガイドで switch 式、record 型完全ガイドで不変オブジェクトの簡潔な定義、init 専用プロパティ完全ガイドでオブジェクト初期化子との連携を解説しています。