C#のstaticメソッドとinstanceメソッドの違いをまとめてみました。
メソッド内でのアクセス範囲
public class Hoge
{
private int piyo = 1;
private static int piyo2;
public void Fuga1(int i)
{
piyo = 2;
piyo2 = 3;
}
public static void Fuga2(int i)
{
// 静的でないフィールド、メソッド、またはプロパティで、オブジェクト参照が必要です
// piyo = 2;
piyo2 = 3;
}
}
staticメソッド内では、non-staticなフィールド(プロパティ・メソッド)にアクセスすることが出来ません。
逆に、instanceメソッド内では、static/non-staticともにアクセス可能です。
メソッドの修飾
public abstract class Super
{
public abstract void Method1();
public virtual void Method2() { }
// 静的メンバーを override、virtual、または abstract とすることはできません。
// public abstract static void Method3();
public static void Method4() { }
}
public class Sub : Super
{
public override void Method1() { }
public new void Method2() { }
// newキーワードによる隠蔽は可能
public new static void Method4() { }
}
staticメソッドは、abstract/virtualとすることができません。したがって、overrideすることもできません。
ただし、newによる隠蔽は可能です。また、staticメソッドをもってinterfaceの実装とすることもできません。
メソッドの呼び出し
static void Main(string[] args)
{
var i = 1;
// 静的メソッドはクラス修飾で呼び出せます。
Hoge.Fuga2(i);
// インスタンスメソッドは、インスタンス修飾で呼び出せます。
var hoge = new Hoge();
hoge.Fuga1(i);
// 静的メソッドをインスタンス修飾で呼び出すことはできません。
// hoge.Fuga2(i);
}
staticメソッドは、クラス修飾で呼び出せますが、インスタンス修飾では呼び出すことはできません。
IL
さて、C#上での比較はそれぐらいにして、ちょっとILに潜ってみます。
(本当はこれがやりたかった。
元のメソッドはこんな感じで定義。
public void Fuga1(int i)
{
Console.WriteLine(i);
}
public static void Fuga2(int i)
{
Console.WriteLine(i);
}
それをILにしてみたのが下記です。
.method public hidebysig
instance void Fuga1 (
int32 i
) cil managed
{
// Method begins at RVA 0x207e
// Code size 9 (0x9)
.maxstack 8
// (no C# code)
IL_0000: nop
// Console.WriteLine(i);
IL_0001: ldarg.1
IL_0002: call void [mscorlib]System.Console::WriteLine(int32)
// (no C# code)
IL_0007: nop
IL_0008: ret
} // end of method Hoge::Fuga1
.method public hidebysig
static void Fuga2 (
int32 i
) cil managed
{
// Method begins at RVA 0x2088
// Code size 9 (0x9)
.maxstack 8
// (no C# code)
IL_0000: nop
// Console.WriteLine(i);
IL_0001: ldarg.0
IL_0002: call void [mscorlib]System.Console::WriteLine(int32)
// (no C# code)
IL_0007: nop
IL_0008: ret
} // end of method Hoge::Fuga2
違いは、staticで修飾されているか?instanceで修飾されているか?の他に、
IL_0001の命令が、「ldarg.1」と「ldarg.0」で異なっているのがわかると思います。
もう少し違うバージョンのILも見てみましょう。
.method public hidebysig
instance void Fuga1 (
int32 i
) cil managed
{
// Method begins at RVA 0x207e
// Code size 21 (0x15)
.maxstack 8
// (no C# code)
IL_0000: nop
// Console.WriteLine(this.piyo);
IL_0001: ldarg.0
IL_0002: ldfld int32 ConsoleApp1.Hoge::piyo
IL_0007: call void [mscorlib]System.Console::WriteLine(int32)
// (no C# code)
IL_000c: nop
// Console.WriteLine(i);
IL_000d: ldarg.1
IL_000e: call void [mscorlib]System.Console::WriteLine(int32)
// (no C# code)
IL_0013: nop
IL_0014: ret
} // end of method Hoge::Fuga1
フィールドのコンソール出力メソッドを追加してみました。
インスタンスフィールドにアクセスするために、IL_0001で「ldarg.0」の命令を発行しています。
つまり、インスタンスメソッドはC#で書いた引数の前、0番目の引数としてクラスインスタンスが渡されているのがわかると思います。
呼び出し部分も確認してみましょう。
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 25 (0x19)
.maxstack 2
.entrypoint
.locals init (
[0] int32,
[1] class ConsoleApp1.Hoge
)
// (no C# code)
IL_0000: nop
// int i = 1;
IL_0001: ldc.i4.1
IL_0002: stloc.0
// Hoge.Fuga2(i);
IL_0003: ldloc.0
IL_0004: call void ConsoleApp1.Hoge::Fuga2(int32)
// (no C# code)
IL_0009: nop
// Hoge hoge = new Hoge();
IL_000a: newobj instance void ConsoleApp1.Hoge::.ctor()
IL_000f: stloc.1
// hoge.Fuga1(i);
IL_0010: ldloc.1
IL_0011: ldloc.0
IL_0012: callvirt instance void ConsoleApp1.Hoge::Fuga1(int32)
// (no C# code)
IL_0017: nop
IL_0018: ret
} // end of method Program::Main
staticメソッドの呼び出しは、変数iをスタックに積んでいるだけですが(IL_0003~)
instanceメソッドの呼び出しには、クラスインスタンスもスタックに積んでいることがわかります。(IL_0010)
#まとめ
メソッド内の実装および呼び出し部分を見れば、instanceメソッドはstaticメソッドの第1引数の前にクラスインスタンスを渡したものとほぼ同じです。
ただし、オブジェクト指向的振る舞い(virtual/override/interface周り)が違うので、そのまま代用はできません。