[C#]絶対名前空間を使ってはいけないC#

`C#`でプログラムを書くときは当たり前のように名前空間を使用していると思います。

例えば以下のような場合、`System`名前空間を使用しています。

```csharp
System.Console.WriteLine("test");
```

プログラムの先頭に`using System;`を書いている場合は毎回`System`を書きませんがこれは先頭部分で名前空間を使用しています。
今回は名前空間を全く使用せずにプログラミングができるかどうかを検証していきます。

## レギュレーション

レギュレーションは以下の通りです。

- クラスにアクセスするときに名前空間をつけてはならない
- `using`ディレクティブを使用してはならない
- `dynamic`を使用しない

上記以外のことなら何をしても:ok_hand:です。
`dynamic`を使用しないというレギュレーションは特に意味ありませんが`dynamic`無しのほうが面白そうなのでつけました。

## ターゲットフレームワーク

.NET Core 2.0

## ゴール

文字列を画面に表示する。

## 名前空間禁止の難しさ

全く名前空間を使わないのはなかなか難しいです。
基本的に標準ライブラリのクラスに直接アクセスすることはできません。
名前空間を書かなくてもアクセスできるものは以下の通りです。

- `int`や`string`のようなビルトインタイプ
- `typeof`式による`System.Type`
- 配列やタプルのようにリテラルが用意されているもの
- 上記のものからメンバを通してアクセスできるもの

これ以外のものは使いたくても使えません。
例えば`List`も`Dictionary`も何一つとして使用することができません。
既存のインターフェースを実装した型を作成することもできません。

## 実装する

### 基本方針 

画面に文字を表示するためには`System.Console.WriteLine(System.String)`を呼び出す必要があります。
何とかしてこれをリフレクション経由で呼び出します。

リフレクションと聞くと`typeof(System.Console)`と行きたいところですがこれはできません。
どうにかして`System.Console`の`System.Type`を取得しなければなりません。

今回は`System.Reflection.Assembly`を通して`System.Console`を取得する方法を考えてみます。
通常`System.Reflection.Assembly`は`System.Reflection.Assembly.GetEntryAssembly`や`System.AppDomain`を通して取得します。
今回はこの手法は使用できないため、`typeof(XXX).Assembly`を使用することにしました。
`typeof(XXX).Assembly`ではそのタイプが定義されている`System.Reflection.Assembly`のインスタンスを取得することができます。

### タイプを探す手法の考察

アセンブリからタイプ情報を探すためにはどうしたらいいでしょうか。
アセンブリのメンバ関数には`GetTypes`という関数があります。
この関数はアセンブリに含まれるタイプ情報を列挙する関数なのでこれを使用すればタイプ情報を探すことができそうです。
しかし、`GetTypes`で取得できるのはアセンブリ内のクラスのみです。
今回の場合では`System.Console`が含まれるアセンブリへの参照が取得できなければなりません。
なのでタイプを探す前にまずアセンブリを探す必要があります。

もう一度アセンブリのメンバ関数を見てみると`GetReferencedAssemblies`という関数があります。
この関数はアセンブリが参照しているアセンブリの名前を返すというものです。
名前が分かってもアセンブリへの参照が取得できなければ意味がありません。
そこでアセンブリの名前から`System.Reflection.Assembly.Load`を使用してアセンブリをロードしアセンブリの参照を取得するという方法をとります。
(本当は`System.Console`だけでよいなら存在するアセンブリはバージョンごとに固定なので`GetReferencedAssemblies`は必要ないですが汎用的にするためにこの手法をとりました。)

### アセンブリのロード

アセンブリのロードも簡単ではなく、ここでもリフレクションをフル活用する必要があります。
まず`System.Reflection.Assembly.GetEntryAssembly`の`MethodInfo`を取得するのですが、
そのためには`System.Reflection.Assembly`のタイプ情報が必要です。
`System.Reflection.Assembly`のタイプ情報を取得は`System.Reflection.Assembly`のインスタンスから`GetType`関数で取得することができます。

```csharp
var assemblyType = typeof(Program).Assembly.GetType();
```

と言いたいところですが実際には`typeof(Program).Assembly.GetType()`で帰ってくるクラスは`System.Reflection.RuntimeAssembly`のインスタンスになっています。
このままでは`GetMethods`を呼び出しても目的の`MethodInfo`を取得することができません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/42193/7bdbfb51-e76c-a3db-2a11-df6d48be9bf9.png)


`System.Reflection.RuntimeAssembly`は`System.Reflection.Assembly`のサブクラスなので`BaseType`を取得して使うことにします。


```csharp
var assemblyType = typeof(Program).Assembly.GetType().BaseType;
```

`MethodInfo`の検索は文字列比較で行います。

```csharp
// 型を書けないので変数をnullで初期化できない
// 絶対にnullが返ってくる引数でGetMethodを呼び出して型推論させる
var assemblyLoadMethodInfo = assemblyType.GetMethod("XXXX");
// stringを引数に使うほうならGetMethodでも大丈夫だけどAssemblyNameのほうを使用したかったので使わない
// var assemblyLoadMethodInfo = assemblyType.GetMethod("Load", new[] { typeof(string) });
foreach (var method in assemblyType.GetMethods())
{
    if (method.ToString() == "System.Reflection.Assembly Load(System.Reflection.AssemblyName)")
    {
        assemblyLoadMethodInfo = method;
        break;
    }
}
```

これで好きにアセンブリのロードができます:point_up:

```csharp
foreach (var assemblyName in assembly.GetReferencedAssemblies())
{
    var loadedAssembly = assemblyLoadMethodInfo.Invoke(null, new[] { assemblyName });
}
```

実はまだ問題があります。
`.NET Core 2.0`でこのコードを実行すると例外が発生します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/42193/59888663-f918-8160-f7d2-b82a4ab48693.png)

`TargetException`でググるとインスタンスのメソッドを第一引数`null`で呼んでいるなどの情報が見つかるのですが、今回の場合は確実にスタティックなメソッドです。
スタックトレースを見ると`RuntimeMethodInfo.InvokeArgumentsCheck`が例外を投げていることが分かりました。
ソースコードを参考にするとどうやら`m_invocationFlags`に`INVOCATION_FLAGS_NO_INVOKE`が入っていると例外が投げられるようです。

https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs#L495

打つ手がなくなった気がするのですがとりあえず`m_invocationFlags`の`INVOCATION_FLAGS_NO_INVOKE`をリフレクションを使って削除できるかどうか試してみます。

ただしこれも簡単ではありません。
`m_invocationFlags`はフィールドなので`GetField`関数を使うのですが、パブリックではないため`System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic`フラグを指定しなければなりません。
フラグの論理和はとても難しいです。
単一のフラグでいいなら`GetEnumValues`で何とかなりますが論理和の場合はそうもいきません。
これを作るためには`Enum.ToObject`を使用する必要があります。
またリフレクションです。
`Enum`のタイプ情報の取得は既存の`Enum`のインスタンスから`GetType`で取得します。
あまりに面倒なので詳細は省きます。
下記の実装を参考にしてください。

```csharp
// Instance | NonPublicのフラグ作成
var bindingFlagsType = assemblyType.Assembly.GetType("System.Reflection.BindingFlags");
// 既存のEnum(MemberType)からenumの型情報取得
var enumType = assemblyLoadMethodInfo.MemberType.GetType().BaseType;
var toObjectMethodInfo = enumType.GetMethod("ToObject", new[] { enumType.GetType().BaseType, typeof(int) });
const int BINDING_FLAGS_INSTANCE_AND_NONPUBLIC = 36; 
var instanceAndNonPublic = toObjectMethodInfo.Invoke(null, new object[] { bindingFlagsType, BINDING_FLAGS_INSTANCE_AND_NONPUBLIC });

var typeType = assemblyLoadMethodInfo.GetType().GetType();
var getFieldMethodInfo = typeType.GetMethod("GetField", new[] { typeof(string), bindingFlagsType });
var invocationFlagsField = getFieldMethodInfo.Invoke(assemblyLoadMethodInfo.GetType(), new object[] { "m_invocationFlags", instanceAndNonPublic });

// invocationFlagsFieldがobjectなのでGet/Setもリフレクション経由
var filedGetValueMethodInfo = invocationFlagsField.GetType().GetMethod("GetValue", new[] { typeof(object) });
var filedSetValueMethodInfo = invocationFlagsField.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) });

// INITIALIZEDのフラグ設定
var originalInvocationFlags = filedGetValueMethodInfo.Invoke(invocationFlagsField, new object[] { assemblyLoadMethodInfo });
const int INVOCATION_FLAGS_INITIALIZED = 0x00000001;
var invocationFlags = toObjectMethodInfo.Invoke(null, new object[] { originalInvocationFlags.GetType(), INVOCATION_FLAGS_INITIALIZED });
filedSetValueMethodInfo.Invoke(invocationFlagsField, new object[] { assemblyLoadMethodInfo, invocationFlags });
```

もうすでに何をやっていたか忘れかけてきましたがアセンブリのロード時にエラーが出ていたのでした。
正直この`m_invocationFlags`を書き換えられないと思っていたのですが実行しても特にエラーが起きずに書き換えられてしまいました。
これによってアセンブリのロード時のエラーもなくなりました。

### 実際にタイプを探す

ここまで来たら後はもう消化試合です。
再帰しながらアセンブリをロードして目的のタイプを探すだけです。

```csharp:タイプを探す再帰関数
public static object FindType(object assembly, object assemblyLoadMethodInfo, string typeName)
{
    var getTypeMethodInfo = assembly.GetType().GetMethod("GetType", new[] { typeof(string) });
    var consoleType = getTypeMethodInfo.Invoke(assembly, new[] { typeName });
    if (consoleType != null)
    {
        return consoleType;
    }
    var getReferencedAssembliesMethodInfo = assembly.GetType().GetMethod("GetReferencedAssemblies");
    var assemblies = getReferencedAssembliesMethodInfo.Invoke(assembly, new object[0]) as object[];
    foreach (var refAssembyName in assemblies)
    {
        var invokeMethodInfo = assemblyLoadMethodInfo.GetType().GetMethod("Invoke", new [] { typeof(object), typeof(object[]) });
        var refAssembly = invokeMethodInfo.Invoke(assemblyLoadMethodInfo, new object[] { null, new object[] { refAssembyName } });
        consoleType = FindType(refAssembly, assemblyLoadMethodInfo, typeName);
        if (consoleType != null)
        {
            return consoleType;
        }
    }

    return null;
}
```

```csharp:使用する部分
var consoleType = FindType(assembly, assemblyLoadMethodInfo, "System.Console");
var getMethodMethodInfo = consoleType.GetType().GetMethod("GetMethod", new[] { typeof(string), (new []{ typeof(string) }).GetType() });
var writeLineMethodInfo = getMethodMethodInfo.Invoke(consoleType, new object[] { "WriteLine", new[] { typeof(string) } });
var invokeMethodInfo = writeLineMethodInfo.GetType().GetMethod("Invoke", new [] { typeof(object), typeof(object[]) });
invokeMethodInfo.Invoke(writeLineMethodInfo, new object[] { null, new object[] { "Hello, World!!" } });
```

ネームスペースが書けないので引数が全部`object`になってたりしますが些細な問題です。
すべての関数はリフレクション経由で呼び出せばいいだけです。
これで終了です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/42193/edef0416-d2a4-885e-cb1b-5f0a330709d2.png)

:cold_sweat:????????

よくわからないですが、実際にプログラム中で`System.Console`が使用されていないと参照アセンブリに含まれないようです。:ghost:
仕方ないので`GetReferencedAssemblies`を捨てて直接`System.Console`を読み込みます。

```csharp
// アセンブリのロードは文字列直接のほうを使う
var assemblyLoadMethodInfo = assemblyType.GetMethod("Load", new[] { typeof(string) });

// アセンブリのロードとタイプの取得
var consoleAssembly = assemblyLoadMethodInfo.Invoke(null, new[] { "System.Console" });
var getTypeMethodInfo = consoleAssembly.GetType().GetMethod("GetType", new[] { typeof(string) });
var consoleType = getTypeMethodInfo.Invoke(consoleAssembly, new[] { "System.Console" });
```

今度こそ終わりです:clap:

![image.png](https://qiita-image-store.s3.amazonaws.com/0/42193/042fa2b0-2b52-67ec-6bc7-f22cecabd984.png)

## まとめ

とても大変だった割にはあんまりおもしろくなかったかもしれません。
私の知識不足で微妙な感じになってしまいました。
今回は文字列表示だけでしたがリフレクションを駆使すればもっと高度なこともできるのではないでしょうか。
通常の方法では例外のキャッチを実現することはできないと思いますが、ILの動的生成を使えば解決可能なのかなという気がします。
(やる気はないので他の人に任せます。)

ちなみにターゲットを`.NET Framework`に変更すると`System.Console`が`mscorlib`に含まれるため`GetReferencedAssemblies`の手法で動きます。
また`System.Console`アセンブリロードができないため`.NET Core`用のコードのままでは失敗してしまいます。

今回書いたソースコードは以下にあります。
[yaegaki/CSharp-HelloWorld-WithoutNamespace](https://github.com/yaegaki/CSharp-HelloWorld-WithoutNamespace)