ネコのために鐘は鳴る

寺院に住み着くパソコ〇好き

(C#) Obsolete によるコンパイルエラーを無視する

おことわり

ハック記事です。非推奨 API は呼び出すべきではありません。

非推奨属性 (Obsolete)

System.ObsoleteAttribute はメソッドやクラスが非推奨であることをマークする属性です。引数の有無によって警告にするかコンパイルエラーにするかを選べます。

メソッドにつければそのメソッドのみ、クラスにつけるとクラス全体が非推奨になります。

// 引数無しは警告
[Obsolete]
public void Foo1()
{
}

// メッセージ付き警告
[Obsolete("Use another method because ...")]
public void Foo2()
{
}

// 第二引数を true にするとコンパイルエラー
[Obsolete("Use another method because ...", true)]
public void Foo3()
{
}

この非推奨マークはコンパイラへの注釈であって、コンパイル結果には影響しません。 つまり、実際にはコンパイルは行われているため、たとえコンパイルエラーとしてマークされていても通常ではない方法で呼び出せます。

Reflection

リフレクションによる呼び出しは Obsolete 属性を無視します。たとえコンパイルエラーに指定されていても何も問題なく呼び出せます。

// 実行できる
typeof(Foo).GetMethod("Bar")!.Invoke(null, null);

public class Foo
{
    [Obsolete("obsolete", true)]
    public static void Bar()
    {
        Console.WriteLine("aaa");
    }
}

Obsolete からの呼び出し

Obsolete 属性のついたメソッド・クラスは、同じく Obsolete 属性のついたものからは警告・エラーなく呼び出せるという仕組みになっています。 最終的に有効なコードから非推奨APIを呼び出そうとする部分で警告・エラーが出るため、一般的には問題なさそうに見えます。

ところが、この連鎖的な呼び出しのチェックは警告なのかコンパイルエラーなのかを区別しません。

つまり、有効なコード→非推奨 API (警告)→非推奨 API (コンパイルエラー) の順で呼び出しを書くと、一つ目の呼び出し箇所で警告は出ますがコンパイルエラーにはならずに二つ目の非推奨APIを実行できます。

ついでにコンパイルエラーは抑制できませんが、警告なら抑制できますね。(#pragma warning disable 0618)

// Bar2 の呼び出しに対する警告は出るが、間接的に Bar を呼び出して実行できる
Foo.Bar2();

public class Foo
{
    [Obsolete("obsolete", true)]
    public static void Bar()
    {
        Console.WriteLine("aaa");
    }

    [Obsolete("obsolete")]
    public static void Bar2()
    {
        Bar();
    }
}

Module Initializer

ModuleInitializer としてマークされたメソッドは Obsolete であってもエラーにならず、モジュール初期化処理として機能できます。 非推奨を回避するという使い方ではないですが参考までに。

詳細はこちら

おまけ 拡張メソッドのクラス

拡張メソッドを記述しているクラスは、クラスに Obsolete 属性をつけていても拡張メソッドは生きています。 非推奨にしたい場合は各拡張メソッドに属性をつける必要があるので注意。

var foo = new Foo();

// 拡張メソッドの記述クラスがコンパイルエラーに指定されていても、
// 拡張メソッドはエラーにならない
foo.Bar();

// 拡張じゃない呼び出しをしている場合はエラー
FooExtensions.Bar(foo)


public class Foo
{
}

[Obsolete("obsolete", true)]
public static class FooExtensions
{
    public static void Bar(this Foo foo)
    {
        Console.WriteLine("aaa");
    }
}

さいごに

非推奨 API は呼び出すべきではありません。