varはC#でローカル変数の型を自動的に推論してくれる便利なキーワードです。
しかし「いつでもvarでよい」わけではなく、使うべき場面・避けるべき場面を理解することが大切です。
この記事では、型安全やdynamicとの違い、ルールとベストプラクティス、落とし穴、ガイドライン、実例とアンチパターンまで丁寧に解説します。
varとは?C#の暗黙の型推論の基本(初心者向け)
varはローカル変数専用のキーワード
var
は「型が不明」ではなく「右辺から型を推論する」ためのキーワードです。
使える場所はローカル変数の宣言(メソッド内やローカル関数内)に限られます。
フィールド、プロパティ、メソッドの引数や戻り値の型としては使えません。
// OK: ローカル変数
void Example()
{
var message = "Hello"; // string と推論
var count = 123; // int と推論
}
// NG: ローカル以外では使えません
class Sample
{
// var field = 0; // コンパイルエラー
// public var Prop => 0; // コンパイルエラー
// var Add(var x, var y) => x + y; // コンパイルエラー
}
コンパイル時に型が決まる(型安全)
var
はコンパイル時に具体的な型へ確定します。
動的型付けではありません。
したがって、型安全を維持しつつ、冗長な型記述を省略できます。
using System;
class Program
{
static void Print(int x) => Console.WriteLine($"int: {x}");
static void Print(string s) => Console.WriteLine($"string: {s}");
static void Demo()
{
var n = 42; // int
var s = "abc"; // string
Print(n); // int: 42
Print(s); // string: abc
// n = "text"; // コンパイルエラー: すでにintと確定しているため
}
static void Main()
{
Demo();
}
}
int: 42
string: abc
dynamicとの違いと注意点
dynamic
は実行時にメンバー解決が行われる「動的型」。
一方var
はコンパイル時に型が決まる「静的型推論」です。
項目 | var | dynamic |
---|---|---|
型が決まるタイミング | コンパイル時 | 実行時 |
主な用途 | ローカルの型記述を簡潔に | COM/Office相互運用、動的JSON、メタプログラミング |
型安全 | 高い(コンパイルで検出) | 低い(実行時例外の可能性) |
パフォーマンス | 明示的型と同等 | 遅くなる可能性が高い |
利用可能な場所 | ローカル変数のみ | ほぼどこでも可 |
// dynamicの例(実行時エラーになりやすい)
dynamic dyn = "hello";
// dyn.NotExists(); // 実行時にRuntimeBinderException(実装していない場合、存在しないメソッドでエラー)
dynamicは強力ですが、初心者はまずvar
で静的型のまま書くことをおすすめします。
varの基本ルールと使い方
初期化が必須(右辺から型を推論)
var
は右辺の式から型を推論するため、必ず初期化が必要です。
null
単体や型が不明な式では推論できません。
// var x; // コンパイルエラー: 初期化がない
// var y = null; // コンパイルエラー: null からは推論できない
var z = (string?)null; // OK: string? と明示キャストで推論可能
var d = default(DateTime); // OK: DateTime
var b = default(bool); // OK: bool
Console.WriteLine(d == default(DateTime)); // True
True
推論できない代表例
var x = default;
は推論できずエラーです(default(int)
のように型を明示すればOK)。- 無名メソッドやラムダの型は
var
ではなくAction
やFunc<T>
が必要な場合があります(ターゲット型コンテキストが必要)。
型が明確なコンテキストで使う
右辺から型が「見れば分かる」場合にvar
を使うと、読みやすさが上がります。
using System.IO;
// 型が明白で読みやすい
var stream = new FileStream("data.txt", FileMode.Open);
// 型が不明瞭で読み手が困る
var result = service.Execute(); // 何型? 戻り値が読めないと理解が遅れる
// 明示的型で意図を伝えるのも有効
IReadOnlyList<Customer> customers = repository.LoadCustomers();
明示的型と同等のパフォーマンス
var
はコンパイル後に具体的な型へ置き換わります。
ランタイムのコストは明示的型と同一であり、var
を使うこと自体が性能を悪化させることはありません。
varを使うべき場面(メリットとベストプラクティス)
冗長な型名やジェネリックで可読性を上げたいとき
長い型名を繰り返すとノイズが増えます。
var
で簡潔に保つと読みやすくなります。
// 冗長
Dictionary<string, List<Tuple<int, string>>> map =
new Dictionary<string, List<Tuple<int, string>>>();
// 簡潔(推論で同じ型になる)
var map2 = new Dictionary<string, List<Tuple<int, string>>>();
LINQと匿名型の受け取りはvarが必須
匿名型は名前を持たないため、var
で受け取るしかありません。
LINQではvar
が自然です。
using System;
using System.Linq;
using System.Collections.Generic;
var people = new List<(string Name, int Age)>
{
("Alice", 28), ("Bob", 17), ("Charlie", 35)
};
var query = people
.Where(p => p.Age >= 18)
.Select(p => new { p.Name, IsThirtyPlus = p.Age >= 30 });
foreach (var item in query)
{
Console.WriteLine($"{item.Name}: 30+? {item.IsThirtyPlus}");
}
Alice: 30+? False
Charlie: 30+? True
foreach/usingでの簡潔な記述
列挙対象やリソースの型が明白な場合、var
で読みやすくなります。
using System.IO;
// foreach
foreach (var line in File.ReadLines("log.txt"))
{
// line は string と推論
}
// using(C# 8 以降のusing宣言)
using var sr = new StreamReader("data.txt");
// sr は StreamReader と推論
型名が重複する初期化でノイズを減らす
右辺に型が現れているなら、左辺の型名を繰り返す必要はありません。
// 型名の重複
Person p1 = new Person("Taro", 20);
// 重複を避けて簡潔に
var p2 = new Person("Taro", 20);
// 参考: C# 9 以降は target-typed new も選択肢
Person p3 = new("Taro", 20);
varを避ける場面(デメリットと落とし穴)
右辺から型が推測しにくく可読性が下がるとき
戻り値の型が不明瞭なメソッド呼び出しにvar
を使うと、読み手が型を追う手間が増えます。
// 悪い例
var data = client.Do(); // data が何者か分からない
// 良い例(意図を明確に)
Response data2 = client.Do(); // Response と明示
IReadOnlyList<Item> items = repo.LoadItems();
// さらに良い:メソッド名で意図を明確化(後述のリファクタリング参照)
var items2 = repo.LoadItemsAsReadOnly(); // varでも意図が伝わる
数値リテラルの推論に注意(int/double/decimal)
数値リテラルは規則に従って型が決まります。
意図と異なる型になると計算結果が変わることがあります。
using System;
void NumericDemo()
{
var a = 1 / 2; // int / int => int (0)
var b = 1.0 / 2; // double / int => double (0.5)
var c = 1m / 2; // decimal / int => decimal (0.5)
var d = 1f; // float
var e = 10_000_000_000; // long(intに収まらないためlongに推論)
Console.WriteLine($"a={a} ({a.GetType().Name})");
Console.WriteLine($"b={b} ({b.GetType().Name})");
Console.WriteLine($"c={c} ({c.GetType().Name})");
Console.WriteLine($"d={d} ({d.GetType().Name})");
Console.WriteLine($"e={e} ({e.GetType().Name})");
}
NumericDemo();
a=0 (Int32)
b=0.5 (Double)
c=0.5 (Decimal)
d=1 (Single)
e=10000000000 (Int64)
代表的なサフィックス一覧:
リテラル例 | 型 |
---|---|
1 | int(収まらなければ uint/long/ulong へ) |
1u | uint |
1l | long |
1ul | ulong |
1.0 | double |
1f | float |
1m | decimal |
意図しない整数除算や型選択を避けるには、必要に応じてサフィックスやキャストを明示しましょう。
API設計や公開メンバーでは使えない(ローカル以外)
var
はローカル専用なので、公開APIのシグネチャやフィールド、プロパティでは使えません。
公開メンバーでは型を明示して契約を固定しましょう。
public class Api
{
// public var Value { get; set; } // コンパイルエラー
public int Value { get; set; } // OK
// public var Get() => 0; // コンパイルエラー
public int Get() => 0; // OK
public void Set(int x) { } // 引数も型を明示
}
意図しないキャストや拡張メソッド解決の誤解
var
は右辺の「式の静的型」で決まります。
戻り値の静的型が変わると、呼ばれる拡張メソッドが変わることがあり、挙動の違いに気づきにくくなります。
using System;
using System.Collections.Generic;
using System.Linq;
static IEnumerable<int> GetSeq() => new List<int> { 1, 2, 3 }; // IEnumerable<int>
static List<int> GetList() => new List<int> { 1, 2, 3 }; // List<int>
void Demo()
{
var seq = GetSeq(); // 静的型: IEnumerable<int>
var list = GetList(); // 静的型: List<int>(IReadOnlyList<int> を実装)
Console.WriteLine(seq.Kind()); // IEnumerable が選ばれる(静的型に基づく)
Console.WriteLine(list.Kind()); // IReadOnlyList が選ばれる(より具体)
}
Demo();
static class Ext
{
public static string Kind(this IEnumerable<int> _) => "IEnumerable";
public static string Kind(this IReadOnlyList<int> _) => "IReadOnlyList";
}
IEnumerable
IReadOnlyList
右辺の静的型に依存するため、明示的に型を固定したい場合は左辺で型注釈を付けると、拡張メソッド解決やオーバーロード解決の意図が明確になります。
varと明示的型の使い分けガイドライン
チームコーディング規約の例(一貫性を重視)
エディタのヒントや自動整形と合わせて、チームで一貫させましょう。
.editorconfigの例です。
# 右辺から型が明白なら var を使う
csharp_style_var_when_type_is_apparent = true:suggestion
# 組み込み型(int, stringなど)は var を使う(好みに応じて変更)
csharp_style_var_for_built_in_types = true:silent
# 明白でない場合は var を避ける
csharp_style_var_elsewhere = false:warning
# IDEの診断(Visual Studio/VS Code)を強めたい場合
dotnet_diagnostic.IDE0007.severity = warning # var 使用/不使用の提案
可読性重視なら明示的型、冗長回避ならvar
- 明示的型: 戻り値型が推測しにくい、意味づけが重要、境界(API/公開メンバー)など。
- var: 右辺で型が自明、匿名型/LINQ、冗長なジェネリック、foreach/usingなど。
名前付けや補助メソッドで型意図を伝える
メソッド名やプロパティ名に「型のヒント」を埋めると、var
でも誤解を招きにくくなります。
- OpenReadOnlyStream, CreateCustomer, LoadCustomersAsReadOnly など、戻り値の性質を名前で示す。
- 戻り値は
IReadOnlyList<T>
やIEnumerable<T>
のようにインターフェイスを返し、実装詳細に依存しない。
よくある誤解(Q&A): varは型名ではない/性能は同じ
- varは「なんでも入る箱」ですか?
いいえ。右辺から決まる具体的な静的型になります。動的型ではありません。
- varは遅いですか?
いいえ。明示的型と同じです。コンパイル後は同等のILになります。
- varはプロパティや引数に使えますか?
いいえ。ローカル変数専用です。
具体例とアンチパターン(LINQ/匿名型/foreach)
良い例: 型が自明な初期化とLINQクエリ
using System;
using System.Linq;
using System.Collections.Generic;
record Person(string Name, int Age);
class Program
{
static void Main()
{
var people = new List<Person>
{
new("Alice", 28),
new("Bob", 17),
new("Charlie", 35),
new("Diana", 42),
};
var adults = people.Where(p => p.Age >= 18); // IEnumerable<Person>
var summary = adults
.GroupBy(p => p.Age >= 30 ? "30+" : "20s")
.Select(g => new { Group = g.Key, Count = g.Count() })
.OrderBy(x => x.Group);
foreach (var item in summary)
{
Console.WriteLine($"{item.Group}: {item.Count}");
}
}
}
20s: 1
30+: 2
右辺から型が自明で、匿名型を用いた集計結果もvar
で自然に扱えている好例です。
悪い例: 戻り値型が不明瞭なメソッド呼び出し
// 何を返すのかが不明瞭
var result = service.Do(); // 読み手は定義へ飛ばないと型が分からない
var data = repo.Get(); // data の意味が曖昧
// 改善(明示的型を使うか、メソッド名で意図を明確に)
Response result2 = service.Do();
IReadOnlyList<Customer> customers = repo.LoadCustomers();
リファクタリングのコツ: 型を表すメソッド名にする
メソッド名を改善すれば、var
でも意図が伝わります。
// Before
var x = repo.Do();
// After: 名前で型の性質を伝える
var customers = repo.LoadCustomersAsReadOnly(); // IReadOnlyList<Customer> と想像しやすい
var stream = file.OpenReadOnlyStream(); // Stream と想像しやすい
このように、明確な責務と命名により、var
の可読性は大きく向上します。
まとめ
var
はC#のローカル変数で使える強力な型推論機能で、コンパイル時に型が確定する型安全な仕組みです。
匿名型やLINQ、冗長なジェネリック型、foreach/usingなど「型が自明」な文脈では可読性を高めます。
一方で、右辺から型が推測しにくいコード、数値リテラルの暗黙的な型決定、拡張メソッド解決に影響する場面、そしてAPIの公開面では注意や回避が必要です。
チームでは.editorconfigなどの規約で一貫性を保ち、命名や戻り値のインターフェイス化で意図を明示しましょう。
最終的には「読み手が迷わないこと」が基準です。
可読性を損ねない範囲でvar
を積極的に使い、必要に応じて明示的型で補う、そんな使い分けが最も実践的です。