C#は、コンパイル時に厳密な型チェックを行う静的型付け言語です。
しかし、外部ライブラリとの連携やJSONデータの処理など、実行時まで型が確定しないケースでは、この厳密さが開発の妨げになることがあります。
そこで登場するのがdynamic型です。
本記事では、型チェックをあえて回避するdynamicの仕組みから、具体的な活用シーン、そして使用上の注意点までを詳しく解説します。
dynamic型とは何か
C#におけるdynamicは、.NET 4.0で導入された比較的新しい型です。
通常の型がコンパイル時に「どのようなメソッドやプロパティを持っているか」を確認されるのに対し、dynamicとして宣言された変数は、そのチェックを実行時(ランタイム)まで先送りにします。

DLR(動的言語ランタイム)の役割
dynamicを支えている技術が、DLR(Dynamic Language Runtime)です。
これは共通言語ランタイム(CLR)の上で動作するライブラリ群で、PythonやRubyのような動的型付け言語の機能を.NETに追加するために開発されました。
C#のコンパイラは、dynamic型の変数に対する操作を見つけると、それを直接実行するコードではなく、「実行時に型を調べて操作を実行せよ」という指示(ペイロード)を生成します。
これにより、コンパイル時には存在しないメソッドの呼び出しであっても、コードをビルドすることが可能になります。
var型やobject型との違い
よく混同されるのがvarキーワードやobject型です。
これらとdynamicには決定的な違いがあります。
| 特徴 | var | object | dynamic |
|---|---|---|---|
| 型の決定タイミング | コンパイル時 | コンパイル時(objectとして) | 実行時 |
| 静的型チェック | あり | あり | なし |
| キャストの必要性 | 不要 | 必要 | 不要 |
| インテリセンス | 効く | 効く(objectの範囲内) | 効かない |
varはあくまで「型推論」であり、右辺の型が確定した時点でその型として固定されます。
一方でobjectはすべての型の親ですが、特定のメソッドを呼ぶにはキャストが必要です。
これらに対し、dynamicは「何にでもなれるが、実行するまで正解がわからない」という性質を持っています。
dynamicの基本的な使い方
それでは、実際にdynamicを使って型チェックを回避するコードを見ていきましょう。
型チェックの回避と実行時の解決
以下のコードでは、本来なら共通のインターフェースを持っていない異なるクラスに対して、同じメソッド名でアクセスを試みています。
using System;
public class Dog
{
public void Bark()
{
Console.WriteLine("ワンワン!");
}
}
public class Robot
{
public void Bark()
{
Console.WriteLine("ピピピッ!(擬似鳴き声)");
}
}
public class Program
{
public static void Main()
{
// dynamic型を使用することで、コンパイル時のチェックを回避
dynamic myAnimal = new Dog();
PerformBark(myAnimal);
myAnimal = new Robot();
PerformBark(myAnimal);
}
public static void PerformBark(dynamic target)
{
// コンパイル時にはBark()メソッドが存在するかチェックされない
// 実行時にtargetの型を調べ、Barkがあれば実行する
try
{
target.Bark();
}
catch (Exception ex)
{
Console.WriteLine($"エラーが発生しました: {ex.Message}");
}
}
}
ワンワン!
ピピピッ!(擬似鳴き声)
この例では、DogとRobotは継承関係にありませんが、dynamicを使うことで共通のメソッド呼び出しを実現しています。
もしtargetがBarkメソッドを持っていない場合は、実行時にRuntimeBinderExceptionが発生します。

実践的な活用シーン
dynamicは乱用すべきではありませんが、特定のシチュエーションではコードを劇的に簡潔にします。
リフレクションの簡略化
従来のC#で、文字列からメソッド名を指定して実行する「リフレクション」を行う場合、コードは非常に複雑になります。
// リフレクションを使った従来の書き方
var target = new SomeComplexClass();
var method = target.GetType().GetMethod("Execute");
method.Invoke(target, null);
// dynamicを使った書き方
dynamic dTarget = new SomeComplexClass();
dTarget.Execute(); // 内部で自動的にリフレクション的な処理が行われる
dynamicを使うことで、複雑なリフレクションのAPIを隠蔽し、通常のメソッド呼び出しと同じシンタックスで記述できるのが大きなメリットです。
JSONや外部データの処理
Web APIから受け取ったJSONデータを扱う際、あらかじめクラス定義(DTO)を作成するのが一般的です。
しかし、構造が頻繁に変わる場合や、一時的なスクリプトでは、ExpandoObjectとdynamicを組み合わせると便利です。
using System;
using System.Dynamic;
public class DynamicJsonSample
{
public static void Run()
{
// 動的にプロパティを追加できるExpandoObject
dynamic user = new ExpandoObject();
user.Name = "田中太郎";
user.Age = 30;
user.Job = "エンジニア";
Console.WriteLine($"{user.Name}({user.Age})の仕事は{user.Job}です。");
// プロパティの追加も自由自在
user.IsActive = true;
if(user.IsActive)
{
Console.WriteLine("現在アクティブです。");
}
}
}
田中太郎(30)の仕事はエンジニアです。
現在アクティブです。
COM Interop (Office連携など)
ExcelやWordなどのCOMオブジェクトを操作する場合、以前のC#では大量のキャストやType.Missingが必要でした。
dynamicの導入により、これらの操作はVBA(Visual Basic for Applications)に近い感覚で記述できるようになり、コードの可読性が飛躍的に向上しました。
dynamic使用時の注意点とリスク
非常に便利なdynamicですが、型チェックを回避することには相応のリスクが伴います。
実行時エラーの危険性
最大のデメリットは、タイプミスによるエラーをコンパイル時に検知できないことです。
dynamic val = "Hello World";
// 本来はLengthだが、Lenthと書き間違えてもコンパイルは通ってしまう
Console.WriteLine(val.Lenth);
このコードを実行すると、Microsoft.CSharp.RuntimeBinder.RuntimeBinderExceptionがスローされ、プログラムはクラッシュします。
静的型付けの「コンパイル時にバグを見つける」という恩恵を完全に捨て去ることになるため、テストの重要性が増します。
パフォーマンスの低下
dynamicの操作にはオーバーヘッドが生じます。
実行時にオブジェクトの型を解析し、適切なメソッドを検索し、キャッシュし、呼び出すというプロセスを経るため、通常の静的メソッド呼び出しに比べると低速です。

大量のループ処理(数万回、数億回の計算)の中でdynamicを使用すると、目に見えてパフォーマンスが劣化する可能性があるため注意が必要です。
インテリセンス(入力補完)の消失
IDE(Visual Studioなど)において、dynamic型の変数の後でドット(.)を打っても、メソッドやプロパティの候補は表示されません。
開発者は、対象のオブジェクトがどのようなメンバーを持っているかを完璧に把握しておく必要があります。
dynamicを安全に使うための代替案
型チェックを回避したいが、リスクも抑えたい。
そんな時に検討すべき方法がいくつかあります。
パターンマッチングの活用
C#の最近のバージョンでは、パターンマッチングが非常に強力になっています。
型を特定してから安全に処理を行うことが可能です。
public void Process(object obj)
{
// C# 7.0以降の型パターン
if (obj is string text)
{
Console.WriteLine($"文字列の長さは: {text.Length}");
}
else if (obj is int number)
{
Console.WriteLine($"数値の2倍は: {number * 2}");
}
}
ジェネリクスの検討
もし、特定のインターフェースを実装していることが期待できるのであれば、dynamicではなくジェネリクスを使用すべきです。
// dynamicを使わずに、型安全を保つ
public void Save<T>(T item) where T : IEntity
{
item.Validate();
// 保存処理...
}
まとめ
C#のdynamicは、静的型付けという言語の基本原則に柔軟性をもたらす強力なツールです。
コンパイル時の型チェックを回避することで、リフレクションの簡略化や動的なデータ構造の処理、COM連携などをスムーズに行うことができます。
しかし、その代償として実行時エラーのリスクやパフォーマンスの低下、開発支援機能の喪失といったデメリットを抱えることになります。
モダンなC#開発においては、まずパターンマッチングやインターフェース、ジェネリクスといった静的な解決策を検討し、それでも解決が困難な場合に限り、「局所的に」dynamicを利用するのがベストプラクティスです。
道具の特性を正しく理解し、適材適所で使い分けていきましょう。
