program study story

プログラムの勉強 アウトプット

デリゲート内部で起こっていること

■ 型定義

インスタンスと関数ポインターのペア。
記述されているものと別で、2つのフィールドがあって、
1つは、マルチキャスト用、もう一つは静的メソッドのために使うフィールドが
作られる。

■ デリゲートのインスタンス生成

デリゲート型の変数に対してメソッドを直接渡すような形でデリゲートを作る。

■ 静的メソッドを渡すと遅い

デリゲートはインスタンスメソッドの時に処理が単純で高速になるように作られている。
C#ではインスタンスメソッドの方が圧倒的に利用頻度が高いので、インスタンスメソッドに
対して最適化したほうが、全体としてのパフォーマンスは上がる。

■ カリー化デリゲート

デリゲート越しの静的メソッドの呼び出しを早くする方法が1つある。
カリー化デリゲートという手段を使うと、インスタンスメソッドと同じで
静的メソッドを呼べるようになる。

拡張メソッドは、実体として、第1引数でインスタンスを受け取る構造になっていて
これがインスタンスメソッドの暗黙的なthis引数と同じ受け取り方になっている。

---------------------------------------------------------------------------------------------------

 

 


class Sample
{
public void InstanceMethod(int x)
{
// 引数が1つだけに見えて、実は暗黙的に this を受け取っている
}

// ということで ↑の InstanceMethod は、以下のような静的メソッドと同じ引数の受け取り方をしてる
static void InstanceLikeMethod(Sample @this, int x)
{
}
}

static class SampleExtensions
{
// であれば、こういう拡張メソッドも InstanceMethod と同じ引数の受け取り方になる
public static void ExtensionMethod(this Sample @this, int x)
{
}
}
---------------------------------------------------------------------------------------------------
var x = new Sample();

Action<int> i = x.InstanceMethod;

// 拡張メソッドに対して、インスタンス メソッドと同じようなデリゲートの作り方を認めてる
Action<int> e = x.ExtensionMethod;
---------------------------------------------------------------------------------------------------


拡張メソッドに対して、
インスタンスメソッドと同じようなデリゲートの作り方を認めている。
(x.Eのような書き方を、カリー化デリゲートと呼ぶ)

通常の静的メソッドの場合と違い、前述のトリックのための別処理への分岐もも掛からず、
内部的にも完全に同じ処理になるため、カリー化デリゲートは呼び出しは最速になっている。

---------------------------------------------------------------------------------------------------
(最適化手法1)普通の静的メソッドを拡張メソッドに置き換え
---------------------------------------------------------------------------------------------------
・静的メソッドに対してダミー引数を1つ増やしてわざわざ拡張メソッド化する高速化手法が使える
---------------------------------------------------------------------------------------------------
using System;

static class Program
{
// 普通の静的メソッド
static int F(int x) => 2 * x;


// わざわざ使いもしない第1引数を増やして、拡張メソッドに変更
static int F(this object dummy, int x) => 2 * x;

static void Main()
{
// 静的メソッドからデリゲート作成
Func<int, int> s = F;

// わざわざ null を使ってカリー化デリゲートにする
Func<int, int> e = default(object).F;

// 以下の2つの呼び出しでは、e (カリー化デリゲート)の方が圧倒的に高速
s(10);
e(10);
}
}

---------------------------------------------------------------------------------------------------
(最適化手法2)匿名関数を拡張メソッドに置き換え
---------------------------------------------------------------------------------------------------
・ちょっとした変換処理などに対して、匿名関数を使うよりも拡張メソッドを挟んだほうが
 速くなることもある。
・匿名関数(特にラムダ式)と比べるとはるかに手間がかかる書き方なので、使い勝手は悪いが、
 よっぽど「速度最優先」な場合には有効。
---------------------------------------------------------------------------------------------------

using System;

class Program
{
// Func 越しに何かのインスタンスを取りたい
static void M(Func<string> factory)
{
Console.WriteLine(factory());
}

static void Main()
{
// でも、呼ぶ側としては単に何かインスタンスを1個渡したいだけ
string s = Console.ReadLine();

// そこで、ラムダ式で1段覆って、string から Func<string> を作る
// これだと、匿名関数の仕様から、匿名のクラスが作られて、その new のコストが余計にかかる
M(() => s);

// 一方で、以下のように、拡張メソッドを介することで、カリー化デリゲート(速い)になる
M(s.Identity);
}
}

static class TrickyExtension
{
// 素通しするだけの拡張メソッドを用意
public static T Identity<T>(this T x) => x;
}

---------------------------------------------------------------------------------------------------

 

 

1週間でC#の基礎が学べる本

1週間でC#の基礎が学べる本