Linqの拡張メソッドは色々面白い事ができそうな予感がビンビンする。

ラムダ式はクエリ アーキテクチャの重要な要素の 1 つです。もう 1 つの重要な要素が "拡張メソッド" です。拡張メソッドとは、動的言語のコミュニティでは一般的になった "duck typing" の柔軟性と、静的に型指定される言語のパフォーマンスとコンパイル時検証を組み合わせたものです。サードパーティは拡張メソッドを使用して、新しいメソッドで型のパブリックコントラクトを補強すると同時に、個々の型の作成者はこれらのメソッドの独自の特殊な実装を提供することができます。

既存のクラス/インターフェースに勝手に後付けでメソッドを追加できると理解した。
これは面白そうだ。

とりあえず拡張してみる

あえてStringを拡張w

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LINQTest {
    public static class StringExtender {
        public static string ToKUMARString(this String s) {
            return "( ´・(ェ)・) " + s + "クマー";
        }
    }
}

ToクマーString()を作ってみた。

テスト

        private void test() {
            string[] names = { "Burke", "Connor", "Frank", 
                       "Everett", "Albert", "George", 
                       "Harris", "David" };

            foreach (string item in names)
                Console.WriteLine(item.ToKUMARString());

        }

結果

( ´・(ェ)・) Burkeクマー
( ´・(ェ)・) Connorクマー
( ´・(ェ)・) Frankクマー
( ´・(ェ)・) Everettクマー
( ´・(ェ)・) Albertクマー
( ´・(ェ)・) Georgeクマー
( ´・(ェ)・) Harrisクマー
( ´・(ェ)・) Davidクマー

だははは。おもしれーw

動作原理とか

#Delphien向けに説明するならロード時に自動的にVMT*1を書き換えてるイメージっつー話で終わっちゃうわけだけど。

MSDNから説明を引いてみる

拡張メソッドを使用すると、新規の派生型の作成、再コンパイル、または元の型の変更を行うことなく既存の型にメソッドを "追加" できます。拡張メソッドは特別な種類の静的メソッドですが、拡張された型のインスタンス メソッドのように呼び出します。C# および Visual Basic で作成されたクライアント コードの場合は、拡張メソッドの呼び出しと、型で実際に定義されたメソッドの呼び出しに明確な違いはありません。

最も一般的な拡張メソッドは、既存の System.Collections..::.IEnumerable 型と System.Collections.Generic..::.IEnumerable<(Of <(T>)>) 型にクエリ機能を追加する LINQ 標準クエリ演算子です。標準クエリ演算子を使用するには、最初に using System.Linq ディレクティブを使用して、これらの演算子をスコープに取り込みます。IEnumerable<(Of <(T>)>) を実装するすべての型は、GroupBy、OrderBy、Average などのインスタンス メソッドを持っていると考えられます。List<(Of <(T>)>)、Array などの IEnumerable<(Of <(T>)>) 型のインスタンスの後に "ドット" を入力すると、IntelliSense により、ステートメントの入力候補としてこれらの追加メソッドが表示されます
(略)
コードでは、インスタンス メソッドの構文を使用して拡張メソッドを呼び出します。ただし、コンパイラが生成する中間言語 (IL: Intermediate Language) により、コードは静的メソッドに対する呼び出しに変換されます。したがって、カプセル化の原則には実質的に違反していません。実際に、拡張メソッドは、それらが拡張している型のプライベート変数にはアクセスできません。
(略)
コンパイラは、一致するシグネチャを持つインスタンス メソッドを検出できない場合、一致する拡張メソッド (存在する場合) にバインドします。

  • 適当なクラスのpublicでstaticなメソッドで、第一パラメータにthisで修飾されたものがあったばあい、そのクラスが拡張される。
  • クラスの拡張は拡張メソッドが収められたクラスがロードされたとき。
  • 使用側はusingでその名前空間を指定するだけでおk
  • メソッドのオーバーライドはできない。
  • オーバーロードは可能
  • 同じパラメータリストを持つメソッドが拡張された場合常にオリジナル側が呼び出される
  • コンパイラ的にはILに変換するときに拡張メソッドを触ってる時には静的メソッドコールに変換してる。

んなとこか。
(´・∀・`)へぇほほぅ

ちょっと意地悪してみる。

クマー拡張と同じシグネチャの拡張を足してみる。

namespace LINQTest2{
    public static class StringExtender2 {
        public static string ToKUMARString(this String s) {
            return "( `・(ェ)・) " + s + "クマー!!";
        }

    }
}

ちょっとシャキーン気味なクマー。

んで、テストコード側にはUsing LINQTest2を追加。

  Using LINQTest2;
 //(略)
        private void test() {
            string[] names = { "Burke", "Connor", "Frank", 
                       "Everett", "Albert", "George", 
                       "Harris", "David" };

            foreach (string item in names)
                Console.WriteLine(item.ToKUMARString());

        }

結果

( ´・(ェ)・) Burkeクマー
( ´・(ェ)・) Connorクマー
( ´・(ェ)・) Frankクマー
( ´・(ェ)・) Everettクマー
( ´・(ェ)・) Albertクマー
( ´・(ェ)・) Georgeクマー
( ´・(ェ)・) Harrisクマー
( ´・(ェ)・) Davidクマー

テストプログラムのnamespaceがLINQTestだったから、同じネームスペースのショボクマが適用されたのかなぁ。
ってことでショボクマのnamespaceを切り替えて再テスト。

using System;
using System.Linq;
using System.Text;

namespace MethodExtender.ShoboKuma {
    public static class StringExtender {
        public static string ToKUMARString(this String s) {
            return "( ´・(ェ)・) " + s + "クマー";
        }
    }
}

namespace MethodExtender.ShakiKuma {
    public static class StringExtender2 {
        public static string ToKUMARString(this String s) {
            return "( `・(ェ)・) " + s + "クマー!!";
        }

    }
}

んで、テストコード側にはUsingにショボクマとシャキクマを追加。

using MethodExtender.ShoboKuma;
using MethodExtender.ShakiKuma;
 //(略)
        private void test() {
            string[] names = { "Burke", "Connor", "Frank", 
                       "Everett", "Albert", "George", 
                       "Harris", "David" };

            foreach (string item in names)
                Console.WriteLine(item.ToKUMARString());

        }

コンパイルしたらテストコード側でエラーが出た。

次のメソッドまたはプロパティ間で呼び出しが不適切です: 'MethodExtender.ShakiKuma.StringExtender2.ToKUMARString(string)' と 'MethodExtender.ShoboKuma.StringExtender.ToKUMARString(string)'

(´・∀・`)おやぁ?

試しにテストコードに追加したUsingの一方をコメントアウトしたらちゃんと動いた。

使用側プログラムと同一ネームスペース内にシグネチャ丸かぶりの拡張メソッドがあった場合優先して使われるからエラーにならない説

タイトルなげぇw

試しにショボクマをLINQTestに戻してみる。

namespace LINQTest{
    public static class StringExtender {
        public static string ToKUMARString(this String s) {
            return "( ´・(ェ)・) " + s + "クマー";
        }
    }
}

namespace MethodExtender.ShakiKuma {
    public static class StringExtender2 {
        public static string ToKUMARString(this String s) {
            return "( `・(ェ)・) " + s + "クマー!!";
        }

    }
}

すんなりショボクマ変換された。
(´・∀・`)おやぁ?

更なる意地悪してみるドS疑惑

こんな感じ

namespace Test         <use>   namespace Testee1 
  + class TestClass   ---+--->      + class Testee1   --------->   class StringExtender(ショボクマ)
                         |
                         |      namespace Testee2       
                         +--->      + class Testee2   --------->   class StringExtender2(シャキクマ)

テストコード

namespace LINQTest{
    public class Test{
        private void testAll() {
            string[] names = { "Burke", "Connor", "Frank", 
                       "Everett", "Albert", "George", 
                       "Harris", "David" };

            new Testee1.Testee1().Test(names);
            new Testee2.Testee2().Test(names);
        }
    }
}

namespace Testee1 {
    public class Testee1 {
        public void Test(string[] args) {
            foreach (string item in args)
                Console.WriteLine(item.ToKUMARString());
        }
    }
    public static class StringExtender {
        public static string ToKUMARString(this String s) {
            return "( ´・(ェ)・) " + s + "クマー";
        }
    }
}

namespace Testee2 {
    public class Testee2 {
        public void Test(string[] args) {
            foreach (string item in args)
                Console.WriteLine(item.ToKUMARString());
        }
    }
    public static class StringExtender {
        public static string ToKUMARString(this String s) {
            return "( `・(ェ)・) " + s + "クマー!!";
        }

    }
}

結果

( ´・(ェ)・) Burkeクマー
( ´・(ェ)・) Connorクマー
( ´・(ェ)・) Frankクマー
( ´・(ェ)・) Everettクマー
( ´・(ェ)・) Albertクマー
( ´・(ェ)・) Georgeクマー
( ´・(ェ)・) Harrisクマー
( ´・(ェ)・) Davidクマー
( `・(ェ)・) Burkeクマー!!
( `・(ェ)・) Connorクマー!!
( `・(ェ)・) Frankクマー!!
( `・(ェ)・) Everettクマー!!
( `・(ェ)・) Albertクマー!!
( `・(ェ)・) Georgeクマー!!
( `・(ェ)・) Harrisクマー!!
( `・(ェ)・) Davidクマー!!

(´・∀・`) へぇ。

なんとなくの理解

おなじシグネチャのメソッドが複数あった場合、

  • オリジナルにそのメソッドがあれば必ずオリジナルが使われる
  • 使用側と同じnamespaceに拡張メソッドがあったらそれが優先される
  • 同じシグネチャの拡張メソッドが複数の名前空間にあった場合はコンパイルエラーになる。

んー。
納得なようなちょっと危ないような。

(´・∀・`) 時間が無いのでとりあえずここまで。

*1:VirtualMethodTable