C#のvar型推論はいつ使う?避ける場面と使い分け

varはC#でローカル変数の型を自動的に推論してくれる便利なキーワードです。

しかし「いつでもvarでよい」わけではなく、使うべき場面・避けるべき場面を理解することが大切です。

この記事では、型安全やdynamicとの違い、ルールとベストプラクティス、落とし穴、ガイドライン、実例とアンチパターンまで丁寧に解説します。

varとは?C#の暗黙の型推論の基本(初心者向け)

varはローカル変数専用のキーワード

varは「型が不明」ではなく「右辺から型を推論する」ためのキーワードです。

使える場所はローカル変数の宣言(メソッド内やローカル関数内)に限られます。

フィールド、プロパティ、メソッドの引数や戻り値の型としては使えません。

C#
// 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はコンパイル時に具体的な型へ確定します。

動的型付けではありません。

したがって、型安全を維持しつつ、冗長な型記述を省略できます。

C#
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はコンパイル時に型が決まる「静的型推論」です。

項目vardynamic
型が決まるタイミングコンパイル時実行時
主な用途ローカルの型記述を簡潔にCOM/Office相互運用、動的JSON、メタプログラミング
型安全高い(コンパイルで検出)低い(実行時例外の可能性)
パフォーマンス明示的型と同等遅くなる可能性が高い
利用可能な場所ローカル変数のみほぼどこでも可
C#
// dynamicの例(実行時エラーになりやすい)
dynamic dyn = "hello";
// dyn.NotExists(); // 実行時にRuntimeBinderException(実装していない場合、存在しないメソッドでエラー)

dynamicは強力ですが、初心者はまずvarで静的型のまま書くことをおすすめします。

varの基本ルールと使い方

初期化が必須(右辺から型を推論)

varは右辺の式から型を推論するため、必ず初期化が必要です。

null単体や型が不明な式では推論できません。

C#
// 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ではなくActionFunc<T>が必要な場合があります(ターゲット型コンテキストが必要)。

型が明確なコンテキストで使う

右辺から型が「見れば分かる」場合にvarを使うと、読みやすさが上がります。

C#
using System.IO;

// 型が明白で読みやすい
var stream = new FileStream("data.txt", FileMode.Open);

// 型が不明瞭で読み手が困る
var result = service.Execute(); // 何型? 戻り値が読めないと理解が遅れる

// 明示的型で意図を伝えるのも有効
IReadOnlyList<Customer> customers = repository.LoadCustomers();

明示的型と同等のパフォーマンス

varはコンパイル後に具体的な型へ置き換わります。

ランタイムのコストは明示的型と同一であり、varを使うこと自体が性能を悪化させることはありません。

varを使うべき場面(メリットとベストプラクティス)

冗長な型名やジェネリックで可読性を上げたいとき

長い型名を繰り返すとノイズが増えます。

varで簡潔に保つと読みやすくなります。

C#
// 冗長
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が自然です。

C#
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で読みやすくなります。

C#
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 と推論

型名が重複する初期化でノイズを減らす

右辺に型が現れているなら、左辺の型名を繰り返す必要はありません。

C#
// 型名の重複
Person p1 = new Person("Taro", 20);

// 重複を避けて簡潔に
var p2 = new Person("Taro", 20);

// 参考: C# 9 以降は target-typed new も選択肢
Person p3 = new("Taro", 20);

varを避ける場面(デメリットと落とし穴)

右辺から型が推測しにくく可読性が下がるとき

戻り値の型が不明瞭なメソッド呼び出しにvarを使うと、読み手が型を追う手間が増えます。

C#
// 悪い例
var data = client.Do();            // data が何者か分からない

// 良い例(意図を明確に)
Response data2 = client.Do();      // Response と明示
IReadOnlyList<Item> items = repo.LoadItems();

// さらに良い:メソッド名で意図を明確化(後述のリファクタリング参照)
var items2 = repo.LoadItemsAsReadOnly(); // varでも意図が伝わる

数値リテラルの推論に注意(int/double/decimal)

数値リテラルは規則に従って型が決まります。

意図と異なる型になると計算結果が変わることがあります。

C#
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)

代表的なサフィックス一覧:

リテラル例
1int(収まらなければ uint/long/ulong へ)
1uuint
1llong
1ululong
1.0double
1ffloat
1mdecimal

意図しない整数除算や型選択を避けるには、必要に応じてサフィックスやキャストを明示しましょう。

API設計や公開メンバーでは使えない(ローカル以外)

varはローカル専用なので、公開APIのシグネチャやフィールド、プロパティでは使えません。

公開メンバーでは型を明示して契約を固定しましょう。

C#
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は右辺の「式の静的型」で決まります。

戻り値の静的型が変わると、呼ばれる拡張メソッドが変わることがあり、挙動の違いに気づきにくくなります。

C#
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の例です。

INI.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クエリ

C#
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で自然に扱えている好例です。

悪い例: 戻り値型が不明瞭なメソッド呼び出し

C#
// 何を返すのかが不明瞭
var result = service.Do();     // 読み手は定義へ飛ばないと型が分からない
var data = repo.Get();         // data の意味が曖昧

// 改善(明示的型を使うか、メソッド名で意図を明確に)
Response result2 = service.Do();
IReadOnlyList<Customer> customers = repo.LoadCustomers();

リファクタリングのコツ: 型を表すメソッド名にする

メソッド名を改善すれば、varでも意図が伝わります。

C#
// 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を積極的に使い、必要に応じて明示的型で補う、そんな使い分けが最も実践的です。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!