C#匿名型とは?使い方・LINQでの活用・引数や戻り値にできない理由までわかりやすく解説
はじめに
C#の匿名型は、クラス名を定義せずに複数の値をひとまとめにできる機能です。特にLINQで必要な項目だけを抽出するときに活用されます。
たとえば、ユーザー情報から名前と年齢だけを取り出したい場合、専用のクラスを作ることなく次のように記述できます。
C#var user = new
{
Name = "田中",
Age = 30
};
匿名型は手軽に使える一方で、プロパティが読み取り専用になる、基本的にメソッドの引数や戻り値の型として指定できないといった制約があります。
この記事では、C#の匿名型の基本的な使い方、内部的な仕組み、通常のクラスやタプルとの違い、LINQでの活用方法までわかりやすく解説します。
1. C#の匿名型とは
1-1. 匿名型はクラス名を定義せずに使える一時的なデータ型
C#の匿名型とは、明示的にクラス名を定義せず、その場で必要なプロパティを持つオブジェクトを作成できる仕組みです。
通常、名前と年齢を持つデータを扱う場合は、次のようなクラスを定義します。
C#public class User
{
public string Name { get; set; } = "";
public int Age { get; set; }
}
作成したクラスは、次のように利用します。
C#var user = new User
{
Name = "田中",
Age = 30
};
一方、匿名型を使えば、クラスを定義せずに同じようなデータを作成できます。
C#var user = new
{
Name = "田中",
Age = 30
};
newの後ろに型名がなく、直接プロパティを記述している点が特徴です。
匿名型は、プログラム全体で共有する正式なデータ型ではなく、メソッド内などの限られた範囲で一時的にデータをまとめる用途に向いています。
1-2. 匿名型が必要になる場面
匿名型は、主に次のような場面で利用されます。
LINQで必要なプロパティだけを抽出する
一時的な計算結果を複数まとめる
グループ化した結果に集計値を追加する
画面表示やログ出力のためのデータを一時的に作る
専用クラスを作るほどではない小さなデータを扱う
たとえば、商品一覧から商品名と税込価格だけを取り出す場合、専用クラスを作らずに匿名型を使用できます。
C#var results = products.Select(product => new
{
product.Name,
TaxIncludedPrice = product.Price * 1.1m
});
このように、元のオブジェクトとは異なる形のデータを一時的に作る処理を「射影」と呼びます。匿名型はLINQの射影と相性がよく、必要な項目だけを簡潔にまとめられます。
ただし、同じデータ構造を複数のメソッドやクラスで共有する場合は、匿名型ではなく通常のクラスやレコードを定義したほうが適切です。
1-3. 通常のクラス・構造体・タプルとの違い
匿名型と通常のクラス、構造体、タプルには、それぞれ異なる特徴があります。
| 種類 | 型名 | 主な用途 | プロパティの変更 | 引数・戻り値での利用 |
|---|---|---|---|---|
| 匿名型 | 開発者は指定しない | 一時的なデータ、LINQの射影 | 原則不可 | 型として直接指定できない |
| クラス | 定義する | 複数箇所で共有するデータ | 定義による | 利用できる |
| 構造体 | 定義する | 小さな値型データ | 定義による | 利用できる |
| タプル | 型名なしでも利用可能 | 複数の値を簡易的に返す | 利用方法による | 利用できる |
匿名型とタプルは似ていますが、用途が異なります。
匿名型は、プロパティ名を持つオブジェクトとしてデータを扱いたい場合に向いています。
C#var user = new
{
Name = "田中",
Age = 30
};
Console.WriteLine(user.Name);
タプルは、メソッドから複数の値を返したい場合などに向いています。
C#(string Name, int Age) GetUser()
{
return ("田中", 30);
}
var user = GetUser();
Console.WriteLine(user.Name);
匿名型には開発者が指定できる型名がないため、公開メソッドの引数や戻り値には適していません。複数の値をメソッドから返したいだけであれば、タプルや専用のクラス、レコードを使用するのが一般的です。
1-4. 匿名型を利用できるC#・.NETのバージョン
匿名型はC# 3.0で導入された機能です。LINQと同じタイミングで追加され、当初は.NET Framework 3.5環境で広く利用されるようになりました。
C# 3.0以降であれば匿名型を利用できるため、現在使用されている.NETやC#の多くの環境で使用できます。
基本構文は導入当初から大きく変わっていません。
C#var item = new
{
Id = 1,
Name = "サンプル"
};
ただし、利用できるC#の機能はプロジェクトに設定された言語バージョンによって決まります。非常に古い環境を利用している場合は、プロジェクト設定や対象フレームワークを確認してください。
2. C#で匿名型を作成する基本的な使い方
2-1. newとオブジェクト初期化子を使った書き方
匿名型は、newとオブジェクト初期化子を組み合わせて作成します。
基本的な構文は次のとおりです。
C#var 変数名 = new
{
プロパティ名1 = 値1,
プロパティ名2 = 値2
};
実際のコードでは、次のように記述します。
C#var product = new
{
Name = "ノートパソコン",
Price = 120000
};
このコードでは、NameとPriceという2つのプロパティを持つ匿名型のオブジェクトが作成されます。
各プロパティの型は、代入された値からコンパイラが推論します。
NameはstringPriceはint
そのため、プロパティの型を明示的に記述する必要はありません。
2-2. 匿名型のプロパティを取得する方法
匿名型のプロパティには、通常のクラスと同じようにドット演算子を使ってアクセスできます。
C#var product = new
{
Name = "ノートパソコン",
Price = 120000
};
Console.WriteLine(product.Name);
Console.WriteLine(product.Price);
実行結果は次のようになります。
ノートパソコン
120000
匿名型であっても、コンパイラは各プロパティの名前と型を認識しています。そのため、Visual Studioなどの開発環境では入力補完やコンパイル時の型チェックが機能します。
存在しないプロパティにアクセスすると、コンパイルエラーになります。
C#Console.WriteLine(product.Stock);
匿名型はdynamicとは異なり、コンパイル時に型が決定される静的型付けの仕組みです。
2-3. プロパティ名を省略する方法
匿名型のプロパティには、既存の変数名やプロパティ名をそのまま利用できます。この書き方は「射影初期化子」と呼ばれます。
C#string name = "田中";
int age = 30;
var user = new
{
name,
age
};
この匿名型には、変数名と同じname、ageというプロパティが作成されます。
次のコードと同じ意味です。
C#var user = new
{
name = name,
age = age
};
一般的なC#の命名規則に合わせて、匿名型側のプロパティ名を大文字から始めたい場合は明示的に指定します。
C#var user = new
{
Name = name,
Age = age
};
オブジェクトのプロパティを匿名型にそのまま取り出す場合も、プロパティ名を省略できます。
C#var user = new User
{
Name = "田中",
Age = 30
};
var summary = new
{
user.Name,
user.Age
};
この場合、匿名型にはNameとAgeというプロパティが作成されます。
2-4. 複数のプロパティを持つ匿名型の作り方
匿名型には、必要な数だけプロパティを定義できます。
C#var employee = new
{
Id = 1001,
Name = "佐藤",
Department = "開発部",
Age = 28,
IsActive = true
};
各プロパティには異なる型の値を指定できます。
C#Console.WriteLine(employee.Id); // int
Console.WriteLine(employee.Name); // string
Console.WriteLine(employee.Age); // int
Console.WriteLine(employee.IsActive); // bool
計算式やメソッドの戻り値をプロパティに設定することも可能です。
C#decimal price = 1000m;
decimal taxRate = 0.1m;
var result = new
{
BasePrice = price,
Tax = price * taxRate,
TotalPrice = price * (1 + taxRate),
CreatedAt = DateTime.Now
};
匿名型は、一時的な計算結果に名前を付けてまとめたい場合にも便利です。
2-5. 匿名型の中に匿名型を入れ子にする方法
匿名型のプロパティには、別の匿名型を指定できます。
C#var user = new
{
Name = "田中",
Address = new
{
Prefecture = "東京都",
City = "新宿区"
}
};
入れ子になった匿名型のプロパティには、ドット演算子を続けてアクセスします。
C#Console.WriteLine(user.Name);
Console.WriteLine(user.Address.Prefecture);
Console.WriteLine(user.Address.City);
配列やコレクションを含めることもできます。
C#var order = new
{
OrderId = 1,
Customer = new
{
Name = "田中",
Email = "tanaka@example.com"
},
Items = new[]
{
new
{
Name = "商品A",
Price = 1000
},
new
{
Name = "商品B",
Price = 2000
}
}
};
ただし、匿名型を何重にも入れ子にするとコードが読みにくくなります。データ構造が複雑になる場合や、複数箇所で利用する場合は、名前付きのクラスやレコードを定義することを検討してください。
2-6. 匿名型の型をvarで受け取る理由
匿名型のオブジェクトは、通常varで受け取ります。
C#var user = new
{
Name = "田中",
Age = 30
};
匿名型には、開発者がコード上で指定できる型名がありません。実際にはコンパイラが型を自動生成しますが、その型名を通常のC#コードから直接記述することはできません。
そのため、次のように型名を書いて変数を宣言できません。
C#// 匿名型の型名を指定できない
UnknownType user = new
{
Name = "田中",
Age = 30
};
varを使うと、右辺の式からコンパイラが匿名型を推論します。varは型をなくす機能ではなく、コンパイラに型名の記述を任せる機能です。
匿名型をobject型で受け取ることはできますが、匿名型固有のプロパティへ直接アクセスできなくなります。
C#object user = new
{
Name = "田中",
Age = 30
};
// user.Nameにはアクセスできない
また、匿名型をメソッドの引数や戻り値の型として直接指定できない理由も、コード上で型名を書けないためです。
次のようなメソッドは定義できません。
C#// 戻り値の型名を指定できない
public 匿名型 GetUser()
{
return new
{
Name = "田中",
Age = 30
};
}
objectやdynamicを使えば形式上は匿名型を渡せますが、型安全性や保守性が低下します。
C#public object GetUser()
{
return new
{
Name = "田中",
Age = 30
};
}
この場合、呼び出し側は戻り値をobjectとして受け取るため、NameやAgeに直接アクセスできません。
C#object user = GetUser();
メソッドの外へデータを渡したい場合は、クラス、レコード、タプルのいずれかを使用するのが基本です。
C#public record UserSummary(string Name, int Age);
public UserSummary GetUser()
{
return new UserSummary("田中", 30);
}
タプルを使う場合は次のように記述できます。
C#public (string Name, int Age) GetUser()
{
return ("田中", 30);
}
3. 匿名型の特徴と内部的な仕組み
3-1. 匿名型のプロパティが読み取り専用になる仕組み
匿名型のプロパティは読み取り専用です。作成時に値を設定できますが、作成後に別の値を代入することはできません。
C#var user = new
{
Name = "田中",
Age = 30
};
// コンパイルエラー
// user.Name = "佐藤";
// user.Age = 31;
コンパイラが生成する匿名型のプロパティには、外部から利用できるsetアクセサーがありません。値はコンストラクターを通して初期化され、その後は変更できない仕組みになっています。
値を変更したい場合は、新しい匿名型のオブジェクトを作成します。
C#var user = new
{
Name = "田中",
Age = 30
};
var updatedUser = new
{
user.Name,
Age = 31
};
なお、匿名型のプロパティ自体は読み取り専用ですが、プロパティが参照するオブジェクトの内部まで不変になるわけではありません。
C#var data = new
{
Items = new List<string> { "A", "B" }
};
data.Items.Add("C");
Itemsプロパティに別のリストを代入することはできませんが、リストの要素を追加・削除することは可能です。
3-2. コンパイラが匿名型のクラスを自動生成する仕組み
匿名型を使用すると、C#コンパイラが内部的なクラスを自動生成します。
次のコードを記述したとします。
C#var user = new
{
Name = "田中",
Age = 30
};
コンパイラは、概念的には次のようなクラスに相当する型を生成します。
C#internal sealed class GeneratedType
{
public string Name { get; }
public int Age { get; }
public GeneratedType(string name, int age)
{
Name = name;
Age = age;
}
}
実際に生成される型名やコードはコンパイラの実装によって異なりますが、匿名型にはおおむね次の特徴があります。
コンパイラが型を生成する
プロパティは読み取り専用
クラスのため参照型として扱われる
Equals、GetHashCode、ToStringが自動生成される通常はアセンブリの外部に公開されない
型名をソースコードで直接指定できない
匿名型の型情報が実行時に存在しないわけではありません。あくまで、開発者がその型名を通常のコードで明示できないという意味です。
3-3. 同じプロパティ構成の匿名型が同じ型として扱われる条件
同一アセンブリ内で、プロパティの名前、型、順序がすべて同じ匿名型は、通常、同じ型として扱われます。
C#var user1 = new
{
Name = "田中",
Age = 30
};
var user2 = new
{
Name = "佐藤",
Age = 25
};
Console.WriteLine(user1.GetType() == user2.GetType());
この例では、どちらも次の構成です。
string型のNameint型のAge
そのため、GetType()で取得した型は同じになります。
値が異なっていても、匿名型の型を決めるのはプロパティの構成です。
C#var item1 = new
{
Id = 1,
Name = "A"
};
var item2 = new
{
Id = 999,
Name = "B"
};
IdとNameの値は異なりますが、名前、型、順序が同じなので同じ匿名型として扱われます。
3-4. プロパティの名前・型・順序が異なる場合の扱い
匿名型は、プロパティの名前、型、順序のいずれかが異なると別の型として扱われます。
プロパティ名が異なる例は次のとおりです。
C#var item1 = new
{
Name = "田中",
Age = 30
};
var item2 = new
{
UserName = "田中",
Age = 30
};
Console.WriteLine(item1.GetType() == item2.GetType());
NameとUserNameが異なるため、別の型です。
プロパティの型が異なる場合も別の型になります。
C#var item1 = new
{
Id = 1
};
var item2 = new
{
Id = "1"
};
item1.Idはint、item2.Idはstringなので、異なる匿名型です。
プロパティの順序も型の構成に影響します。
C#var item1 = new
{
Name = "田中",
Age = 30
};
var item2 = new
{
Age = 30,
Name = "田中"
};
Console.WriteLine(item1.GetType() == item2.GetType());
同じ名前と型のプロパティを持っていても、定義順が異なるため別の型として扱われます。
匿名型の比較やコレクションへの格納では、プロパティの名前、型、順序を統一することが重要です。
3-5. Equals・GetHashCode・ToStringの動作
匿名型では、Equals、GetHashCode、ToStringがコンパイラによって自動的に実装されます。
Equalsは、同じ型の匿名型について、各プロパティの値を順番に比較します。
C#var user1 = new
{
Name = "田中",
Age = 30
};
var user2 = new
{
Name = "田中",
Age = 30
};
Console.WriteLine(user1.Equals(user2));
プロパティの構成と値が同じなので、結果はTrueになります。
値が異なる場合はFalseです。
C#var user1 = new
{
Name = "田中",
Age = 30
};
var user2 = new
{
Name = "田中",
Age = 31
};
Console.WriteLine(user1.Equals(user2));
GetHashCodeも各プロパティの値をもとに計算されます。このため、匿名型はLINQのDistinctやGroupByで複数項目を比較する際に便利です。
C#var users = new[]
{
new { Department = "開発部", Role = "一般" },
new { Department = "開発部", Role = "一般" },
new { Department = "営業部", Role = "管理職" }
};
var distinctUsers = users.Distinct();
foreach (var user in distinctUsers)
{
Console.WriteLine(user);
}
同じプロパティ値を持つ最初の2件は等しいと判断されます。
ToStringを呼び出すと、プロパティ名と値を含む文字列が返されます。
C#var user = new
{
Name = "田中",
Age = 30
};
Console.WriteLine(user);
出力形式は、おおむね次のようになります。
{ Name = 田中, Age = 30 }
デバッグや簡単なログ出力には便利ですが、表示形式はコンパイラの実装に依存します。画面表示や外部連携で厳密な書式が必要な場合は、匿名型のToStringに依存せず、自分で文字列を組み立ててください。
3-6. 匿名型は値型ではなく参照型
C#の匿名型は、構造体のような値型ではなくクラスとして生成される参照型です。
C#var user = new
{
Name = "田中",
Age = 30
};
Console.WriteLine(user.GetType().IsValueType);
結果はFalseになります。
ただし、匿名型ではEqualsがプロパティの値を比較するように実装されています。そのため、参照型であっても、同じ構成と値を持つ匿名型同士は等しいと判定されます。
C#var user1 = new
{
Name = "田中",
Age = 30
};
var user2 = new
{
Name = "田中",
Age = 30
};
Console.WriteLine(ReferenceEquals(user1, user2));
Console.WriteLine(user1.Equals(user2));
user1とuser2は別々に生成されたオブジェクトなので、ReferenceEqualsの結果はFalseです。一方、プロパティの値が同じなので、Equalsの結果はTrueになります。
つまり、匿名型は参照型ですが、等価性の判定については値に基づく動作をします。
4. LINQで匿名型を活用する方法
4-1. select句で必要な項目だけを取り出す
匿名型が特に活躍するのがLINQです。クエリ構文のselect句で匿名型を作成すると、元データから必要な項目だけを取り出せます。
まず、次のようなクラスとデータがあるとします。
C#public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Category { get; set; } = "";
public decimal Price { get; set; }
public int Stock { get; set; }
}
C#var products = new List<Product>
{
new Product
{
Id = 1,
Name = "商品A",
Category = "文房具",
Price = 500,
Stock = 10
},
new Product
{
Id = 2,
Name = "商品B",
Category = "家電",
Price = 5000,
Stock = 3
},
new Product
{
Id = 3,
Name = "商品C",
Category = "文房具",
Price = 800,
Stock = 0
}
};
商品名と価格だけを取り出す場合は、次のように記述します。
C#var results =
from product in products
select new
{
product.Name,
product.Price
};
取得したデータは、匿名型のコレクションです。
C#foreach (var result in results)
{
Console.WriteLine($"{result.Name}: {result.Price}円");
}
元のプロパティを取り出すだけでなく、計算結果を追加することもできます。
C#var results =
from product in products
select new
{
product.Name,
BasePrice = product.Price,
TaxIncludedPrice = product.Price * 1.1m,
IsInStock = product.Stock > 0
};
匿名型は、集計処理にも利用できます。
C#var categorySummaries =
from product in products
group product by product.Category into productGroup
select new
{
Category = productGroup.Key,
ProductCount = productGroup.Count(),
TotalStock = productGroup.Sum(product => product.Stock),
AveragePrice = productGroup.Average(product => product.Price)
};
結果を列挙すると、カテゴリごとの件数や在庫数、平均価格を取得できます。
C#foreach (var summary in categorySummaries)
{
Console.WriteLine(
$"{summary.Category}: " +
$"{summary.ProductCount}件、" +
$"在庫{summary.TotalStock}個、" +
$"平均価格{summary.AveragePrice}円");
}
LINQの処理内でのみ使う一時的な集計結果であれば、専用クラスを定義するよりも匿名型を使うほうが簡潔です。
4-2. メソッド構文のSelectで匿名型を作成する
LINQのメソッド構文では、Selectメソッドのラムダ式内で匿名型を作成します。
C#var results = products.Select(product => new
{
product.Name,
product.Price
});
条件に一致するデータを絞り込んでから匿名型へ変換する場合は、WhereとSelectを組み合わせます。
C#var results = products
.Where(product => product.Stock > 0)
.Select(product => new
{
product.Name,
product.Price,
product.Stock
});
並び替えも組み合わせられます。
C#var results = products
.Where(product => product.Stock > 0)
.OrderBy(product => product.Price)
.Select(product => new
{
product.Name,
product.Price,
StockValue = product.Price * product.Stock
});
グループ化した結果を匿名型にまとめる場合は、次のように記述します。
C#var categorySummaries = products
.GroupBy(product => product.Category)
.Select(group => new
{
Category = group.Key,
ProductCount = group.Count(),
TotalStock = group.Sum(product => product.Stock),
MaximumPrice = group.Max(product => product.Price)
});
複数のキーでグループ化するときは、GroupByのキーにも匿名型を使用できます。
C#var summaries = products
.GroupBy(product => new
{
product.Category,
HasStock = product.Stock > 0
})
.Select(group => new
{
group.Key.Category,
group.Key.HasStock,
Count = group.Count()
});
匿名型ではプロパティの値に基づいてEqualsとGetHashCodeが実装されるため、複数項目をまとめたグループ化キーとして利用できます。
ただし、LINQで作成した匿名型の結果をメソッド外へ公開したり、複数のレイヤーで共有したりする場合は注意が必要です。
次のような処理を公開メソッドにしようとしても、戻り値の匿名型を指定できません。
C#// 匿名型を戻り値の型として直接指定できない
var GetProducts()
{
return products.Select(product => new
{
product.Name,
product.Price
});
}
メソッド外へ返す場合は、専用のクラスやレコードを定義します。
C#public record ProductSummary(
string Name,
decimal Price,
decimal TaxIncludedPrice);
C#public IEnumerable<ProductSummary> GetProducts(
IEnumerable<Product> products)
{
return products.Select(product => new ProductSummary(
product.Name,
product.Price,
product.Price * 1.1m));
}
匿名型はLINQの途中処理や同一メソッド内での利用にとどめ、外部との受け渡しには名前付きの型を使うと、型安全で保守しやすいコードになります。
まとめ
C#の匿名型は、クラス名を定義せずに複数の値をまとめられる便利な機能です。
newとオブジェクト初期化子を使って作成し、変数は通常varで受け取ります。
C#var user = new
{
Name = "田中",
Age = 30
};
匿名型には、次のような特徴があります。
開発者が型名を定義する必要がない
プロパティの型はコンパイラが推論する
プロパティは読み取り専用になる
コンパイラが内部的なクラスを生成する
値型ではなく参照型として扱われる
EqualsやGetHashCodeはプロパティの値に基づいて動作するプロパティの名前、型、順序が同じ匿名型は同じ型として扱われる
LINQの
SelectやGroupByと組み合わせやすい
一方、匿名型にはコード上で指定できる型名がありません。そのため、メソッドの引数や戻り値の型として直接指定することはできません。
匿名型は、LINQの途中結果やメソッド内だけで使用する一時的なデータに適しています。データを複数のメソッドで共有する場合や、外部へ公開する場合は、通常のクラス、レコード、タプルなどを使い分けることが大切です。

