Skip to content

MS_ExtensionMethods

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

拡張メソッド

概要

クラスライブラリ、フレームワーク開発に使用されつつあるので少々調査。

詳細

  • 新規の派生型の作成、再コンパイル、または元の型の変更を行うことなく
    既存の型にメソッドを "追加" できる。
  • 拡張メソッドは特別な種類の静的メソッドですが、
    拡張された型のインスタンス メソッドのように呼び出せる。

補足(実体は静的メソッド呼び出し): 「追加できる」と言っても、
元の型は一切変更されない。コンパイラが呼び出しを書き換えるだけである。

// 書いたコード
"abc".MyUpper();

// 実際に生成される IL
StringExtensions.MyUpper("abc");

このため、

  • private なメンバーには触れない(外部から見える範囲のみ)
  • オーバーライドできない(仮想呼び出しではない)
  • インスタンス メソッドが優先される(同名なら拡張側は呼ばれない)
  • null に対しても呼べるthis が null でも例外にならない)

という性質を持つ。
最後の点は意外に有用で、s.IsNullOrEmpty() のような書き方ができる。

定義と利用

データ列に対するパイプラインやメソッドチェーンを、
後付けで実装するようなユースケースに最も適合する。

定義側

  • static クラスの static メソッドの第 1 引数の前に this を付ける。
  • 同じ名前空間内に 2 つ以上同名の拡張メソッドを定義できない。

利用側

  • インスタンス・メソッドと同じ書き方をする(第 1 引数に this 的にアクセス可能)。
  • 同名の拡張メソッドが定義されている名前空間を同時に using できない。

補足(LINQ がまさにこれ): 原文が言う
「データ列に対するパイプラインやメソッドチェーン」の代表例が LINQ である。

// System.Linq.Enumerable の拡張メソッド群
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T,bool> predicate)

IEnumerable<T> というインターフェイスに
後から数十のメソッドを足したのが LINQ であり、
既存のインターフェイスを壊さずに機能を足すという
拡張メソッドの意義を最もよく示している。

【拡張メソッドが無かったら】
   IEnumerable<T> に Where を足す
     → すべての実装クラスが壊れる(実装漏れ)

【拡張メソッド】
   実装クラスは一切変更不要

現在の C# には インターフェイスの既定実装(default interface methods)も
あるが、こちらはインターフェイス側を変更できる
場合に限られる。

注意

便利なだけに、フツーに考えた範囲で問題ありそう。

using しないと IDE 上に見えてこない。

名前空間を using(Imports)しないと拡張メソッドが IDE 上に見えてこない。

  • 個人的には、名前空間が違うと、using しないと拡張メソッドが
    IDE 上に見えてこないのが辛いと思った。
  • 拡張メソッドは対象とする型の提供者以外は作成するべきではありません - よねKENのプログラミング研究
    http://d.hatena.ne.jp/yone-ken/20090304/p1

補足(発見可能性の問題): これは拡張メソッド最大の実務的な弱点である。
「その型に何ができるか」がインテリセンスから分からない
(using していない名前空間の拡張メソッドは候補に出ない)。

緩和策としては、

手段 内容
対象の型と同じ名前空間に置く 型が見える所では拡張も見える
global using(C# 10+) プロジェクト全体で暗黙に using する
ImplicitUsings SDK が主要な名前空間を自動で using
Visual Studio の候補提示 未 using の拡張メソッドを提案する機能はある

global using の登場により、
「よく使う拡張メソッドの名前空間を全体で通す」ことが容易になった。

// GlobalUsings.cs
global using MyCompany.Extensions;

衝突が起きると、対応策が少々アレ。

https://twitter.com/openhishopjpo/status/1086149348109373440

  • 再定義または削除により、曖昧さを取り除く。
  • 一方の名前空間だけ using(Imports)で取り込む。
  • 両方の名前空間が必要な場合は、完全修飾クラス名によって
    単純に静的クラス呼び出しする。
    同じ名前空間内に対象とするメソッドが定義されていると、
    当然、拡張メソッドとして使い物にならなくなる。

移行メモ(誤字): 原文の「同じ名前空間内に将とするメソッド」は
対象とするの誤変換、
「拡張メソッドとして使ものにならなくなる」は
使い物にならなくなるの脱字と判断し修正した。

補足(衝突時の解決順序): 拡張メソッドの解決は次の順で行われる。

① インスタンス メソッド(あればこれが勝つ。拡張は呼ばれない)
② より「近い」名前空間の拡張メソッド
③ 同じ距離に複数あると → コンパイル エラー(曖昧)

② の「近い」は入れ子の名前空間の内側から外側へという意味で、
using の記述順ではない。

原文が挙げる 3 つ目の回避策(完全修飾で静的呼び出し)が確実である。

// 拡張メソッド構文が使えない場合
MyCompany.Extensions.StringExtensions.MyUpper(s);

① の「インスタンス メソッドが勝つ」は特に注意が要る
ライブラリ側が後から同名のインスタンス メソッドを追加すると、
再コンパイルしただけで呼び先が静かに変わる
原文が引用している「対象とする型の提供者以外は作成するべきではない」
という主張の根拠がここにある。

その他

構造体、インターフェイス、enum に対しても適用可能。

補足: インターフェイスに対して定義できる点が特に重要で、
LINQ が IEnumerable<T> に対して定義されているのはこのためである。
「実装クラスを問わず、共通の操作を後付けする」という使い方ができる。

なお、構造体に対する拡張メソッドはコピーが発生する
this が値渡しになる)ため、
変更を反映したい場合は this ref を使う必要がある(C# 7.2+)。

参考


Tags: 移行, プログラミング, .NET開発

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally