PythonのPydanticとC#を比較してみた + C++ DCOMの思い出

## はじめに
最近、Pythonを使う機会が増えてきて、型ヒントを活用したり、データ構造のバリデーションをしっかり行いたいと思うようになりました。
そんなときに便利なのが Pydantic です。Pythonで型安全を強化するライブラリとして有名ですが、今回はC#との比較を通して、そのメリットや使い勝手を整理してみたいと思います。
さらに、昔C++でDCOMを扱っていた頃の記憶を振り返りながら、型とバリデーションの話をしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/eb71fb63-0ca9-eb32-317d-82d52d93e6c2.png)

## 1. C# での型安全

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/8caa8e9f-d52b-87e5-6efa-15593f7bbf56.png)

* C# は静的型付け言語であり、コンパイル時に型チェックが行われる
* class や struct のプロパティ・メソッドなどを定義する際、型を明確に指定できる
* Null許容型やジェネリクスなど、様々な機能で型安全を保つ仕組みが整備されている
* JSONのデシリアライズなどは System.Text.Json や Newtonsoft.Json などを活用して、クラスやレコードへマッピングできる

例: C# での簡単なクラス定義とJSONデシリアライズ

```csharp
using System;
using System.Text.Json;

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

class Program
{
    static void Main()
    {
        string json = "{\"Name\": \"Alice\", \"Age\": 25}";
        Person p = JsonSerializer.Deserialize<Person>(json);
        Console.WriteLine($"{p.Name} is {p.Age} years old.");
    }
}
```

上記例では Person クラスのプロパティとJSONのプロパティが対応しており、型に沿ってデシリアライズされる。
コンパイル言語であるため、プロパティの型が違うなどのミスはコンパイル時に検知できる。

## 2. Python での型安全

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/3768b955-09c8-ac27-445d-b619426d3bbe.png)

* Python は動的型付け言語で、コードの実行時に型が確定する
* 近年では型ヒント (typing モジュール) が導入され、mypy などの静的解析ツールと組み合わせることでコンパイルエラーのような検知が可能
* それでも、ランタイム時に完全な型チェックが行われるわけではないため、バリデーションは別途行う必要がある

そんな中で、データ構造のバリデーションやランタイム時の型安全を確保しやすくしてくれるのが Pydantic です。

## 3. Pydantic とは？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/1bd2d582-4532-3554-e864-2a30f0b76821.png)


* Python 3.7+ で動作するデータバリデーションライブラリ
* モデルクラス(スキーマ)に型ヒントを記述しておくと、インスタンス生成時に自動でバリデーションを行ってくれる
* バリデーションエラーがある場合は例外を投げてくれるので、異常データにすぐ気づける
* JSONなどのシリアライズ・デシリアライズも簡単に行える

例: Pydantic の基本的な使い方

```python
from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int

# 正しいデータ（バリデーションOK）
person = Person(name="Bob", age=30)
print(person.name, person.age)

# 間違ったデータ（バリデーションNG）
try:
    invalid_person = Person(name="Charlie", age="twenty")
except Exception as e:
    print(e)
```

invalid_person 生成時点で age フィールドが int 型ではないためバリデーションエラー。
実行時でも型チェックが入り、Pythonコードでありながら C# のような厳格な型を扱う感覚を得られる。

## 4. C# と Pydantic (Python) の比較

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/1f5974c7-744a-5a74-9e0e-4eeab9f64c8f.png)

### 4.1 コンパイル時の型チェック

C#:
* コードを書く段階で型の不一致や未定義のプロパティなどをコンパイラが検知

Python + Pydantic:
* コードの構文・型ヒントの整合性は静的解析ツール(mypyなど)を通してある程度検知可能
* 最終的には実行時チェックがメイン

### 4.2 データバリデーション

C#:
* エンティティ・DTO クラスに属性（[Required], [Range] など）をつけてバリデーションすることも多い
* JSONシリアライズ時に JsonSerializer や Newtonsoft.Json を使い、バリデーションは別途ロジックで行うパターンもある

Python + Pydantic:
* BaseModel を継承し、クラス属性で型を指定するだけで基本的なバリデーションを自動で行ってくれる
* 独自のバリデーションルールも簡単に定義可能

### 4.3 静的型付け vs 動的型付け

C#:
* 静的型付けなので、実装段階から「型を厳密に扱う」ことを前提にコードを書く

Python + Pydantic:
* 元々は動的型付けであり、「型を厳密に扱わなくても動く」書き方も可能
* Pydanticを使うことで「厳密に扱う」書き方に寄せられる

### 4.4 運用上の使い分け

C#:
* 大規模システムやチーム開発ではコンパイル時の安全性が大きなメリット

Python + Pydantic:
* 迅速な開発と柔軟性を求めつつ、型の安全性やバリデーションも担保したいときに便利
* マイクロサービスやデータ処理の一部などでは非常に使いやすい

## 5. サンプル比較

C# サンプル:

```csharp
using System;
using System.Text.Json;

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public static class PersonValidator
{
    public static bool Validate(Person p, out string errorMessage)
    {
        if (string.IsNullOrEmpty(p.Name))
        {
            errorMessage = "Name is required.";
            return false;
        }
        if (p.Age < 0 || p.Age > 120)
        {
            errorMessage = "Age must be between 0 and 120.";
            return false;
        }
        errorMessage = string.Empty;
        return true;
    }
}

class Program
{
    static void Main()
    {
        string json = "{\"Name\": \"Bob\", \"Age\": 130}";
        Person p = JsonSerializer.Deserialize<Person>(json);

        if (PersonValidator.Validate(p, out string error))
        {
            Console.WriteLine("Valid person data.");
        }
        else
        {
            Console.WriteLine(error);
        }
    }
}
```

* Person クラスと簡易的な PersonValidator でバリデーションを実装
* JSON→Person 変換とバリデーションを分ける構成

Python (Pydantic) サンプル:

```python
from pydantic import BaseModel, validator
from typing import Optional
import json

class Person(BaseModel):
    name: str
    age: int
    
    @validator("age")
    def validate_age(cls, value):
        if not (0 <= value <= 120):
            raise ValueError("Age must be between 0 and 120.")
        return value

json_str = '{"name": "Bob", "age": 130}'
data = json.loads(json_str)

try:
    person = Person(**data)
    print("Valid person data.")
except Exception as e:
    print(f"Validation error: {e}")
```

* BaseModel を継承した Person クラスで型とバリデーションを一元管理
* JSON→Python辞書→Pydanticモデルの流れで自然にバリデーションを実施

## 6. 昔のC++ DCOMの思い出 (＋COMの型やバリデーション)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/63eb203e-6d53-15ad-9bfe-29487e04f6ec.png)

「**DCOM (Distributed COM)**」と聞いても、ピンとこない方は多いかもしれません。  
しかし実は、Windows でアプリケーション同士をやり取りしたり、ネットワーク越しにコンポーネントを呼び出すといった “分散オブジェクト” を扱うために作られた、かなり大掛かりな仕組みでした。  
「今ではあまり見かけなくなった」と言われがちですが、実際にはレガシーシステムや大規模企業の基幹部分で、DCOM をいまなお使い続けているケースも少なくありません。

### 6.1 COM/DCOM ってどんなもの？

- **COM (Component Object Model)**  
  Microsoft が開発したコンポーネント技術で、ソフトウェアのモジュール化や再利用を促進するために考案されました。  
  - Windows の右クリックメニューの拡張や Office のマクロ連携など、意外と身近なところにも使われていて、実は今でも根っこの部分で動いています。  
  - **DCOM** はその分散版で、ネットワークをまたいで COM オブジェクトを呼び出せる仕組みです。

- **IDL でインターフェース定義**  
  COM/DCOM では、`IDL (Interface Definition Language)` という専用の言語でメソッドシグネチャや引数の型を定義し、これをコンパイルして `C++` のヘッダやプロキシ/スタブなどを生成する形が一般的でした。  
  - `IUnknown` インターフェースを継承してメソッドを実装し、参照カウントを管理したり、`HRESULT` を返して成功・失敗を判定したりと、やるべきことが多かったのも特徴です。

### 6.2 型安全というより “ポインタ管理と参照カウント” との戦い

- **COM のバリデーション事情**  
  COM メソッドの呼び出しでは、返り値として `HRESULT` を返し、エラーがあれば “例外” というより “エラーコード” として扱うのが一般的でした。  
  - `HRESULT` が `S_OK` 以外だとエラー。呼び出す側で if文 でチェック、あるいは `FAILED(hr)` といったマクロで失敗を判定したり……とにかくコードが増えがちでした。  
  - さらに引数や戻り値として `VARIANT` 型（いろいろな型をラップする共用体のようなもの）を受け取る場面も多く、実行時に「これって文字列なの？ 整数なの？」と振り分ける必要もありました。

- **参照カウント漏れやポインタの取り扱い**  
  - 「**IUnknown::AddRef / Release を正しく呼ばないとメモリリークやクラッシュの原因になる**」というのは COM のお約束でした。  
  - いくら IDL で型を定義していても、低レイヤーでポインタ管理をミスると簡単に落ちるので、**型安全というよりメモリアクセスとの戦い**だった面も大きいです。

### 6.3 それでも “分散” オブジェクトは画期的だった

- **当時としては画期的な技術**  
  - ネットワーク越しにオブジェクトを呼び出すための仕組みが標準で用意されるというのは、当時としてはかなり先進的でした。  
  - 一方で、設定が複雑だったり、セキュリティ面の問題などで運用コストも高く、「使いこなすのが大変」という印象を持つ人も多かったです。

- **こんな場面で活躍していた**  
  - Windows サーバー環境で複数のアプリケーションを連携する “分散システム” を構築したり、Excel や Word の自動操作 (Automation) をネットワーク経由で行うなど、レイヤーを意識しなければ便利に使える場面がありました。  
  - ただ、トラブルシューティングが難しく、システムが大きくなるほど原因追跡に苦労するケースもしばしば。

### 6.4 C# や Pydantic との対比

- **バリデーションの自動化が当たり前ではなかった時代**  
  - C# や Pydantic が提供する “型を元にした自動バリデーション” とは違い、COM/DCOM では基本的に “HRESULT で失敗を返すだけ” という素朴な方式が主流でした。  
  - 参照カウントやメモリアクセスを自力で管理しながら、**自分で「この引数は null じゃないよね？」とチェックする** というスタイルは、今の高レベルな開発に慣れるとかなり骨が折れる作業だったと感じます。

- **“昔はこんなに苦労してたんだ” という意外性**  
  - C++ DCOM を経験していると、いかに今の C# や Pydantic のようなフレームワークが高レベルで便利かを実感できます。  
  - 参照カウントやインターフェース定義に頭を悩ませなくても、自動生成される機能やツール、属性アノテーション、例外機構などが揃っているのは、本当にありがたいことですよね。

---

そういうわけで、**C++ DCOM の場合**は、低レイヤーな管理を強いられる開発手法が主流でした。ネットワーク越しの COM を使いこなせるエンジニアは貴重だった反面、トラブル対応や参照カウント漏れのデバッグに悩まされることも多かったのです。  
ただ一方で、いまだにレガシーシステムなどで使われているケースもあるため、知っておくと “古い環境の解析・改修” に役立つことがあります。  

**いまの C# や Python（Pydantic）の高レベルなバリデーションや型管理は本当にありがたい**と実感する次第です。