Obsolete属性は、型やメンバに付与する属性で、ある機能・API(型やメンバ)が廃止予定・非推奨である旨を、コンパイラの警告またはエラーによって通知するためのものです。 Obsolete属性を使うことにより、使用しようとしているAPIが何らかの理由で廃止された、あるいは非推奨となった(obsolete, 時代遅れ、廃れた)ことをコンパイル時に知ることができます。 Obsolete属性は、Javaにおける@Deprecated
アノテーションなどに相当する機能と言えます。
Obsolete属性により、APIの提供者はobsoleteであることを属性としてコード中で記述することができ、一方APIの利用者はobsoleteであることをコンパイル時に警告またはエラーとして知ることができます。 このため、obsoleteであることを示すドキュメント等の整備/参照によらずとも、それをコードのみで通知/把握することができます。
Obsolete属性では単に廃止予定・非推奨の通知をするだけでなく、警告・エラーとして表示されるメッセージも指定できるため、これにより代替機能やより洗練された実装への移行を利用者に対して促すことができます。
ここではObsolete属性についてと、Obsolete属性を使った具体例、機能の廃止・非推奨化について解説します。
Obsolete属性
Obsolete属性が付与された型・メンバを使用しようとすると、コンパイル時に警告またはエラーとして通知されるようになります。
using System;
class Sample {
// Obsolete属性を付与してこのメンバの参照を警告として通知する
[Obsolete("このメソッドの使用は非推奨です")]
static void M1() {}
// Obsolete属性を付与してこのメンバの参照をエラーとして通知する
[Obsolete("このメソッドは廃止されました", error: true)]
static void M2() {}
static void Main()
{
// Obsolete属性により、メンバの参照で警告が発生する
M1(); // warning CS0618: 'Sample.M1()' は旧形式です ('このメソッドの使用は非推奨です')
// Obsolete属性により、メンバの参照でエラーが発生する
M2(); // error CS0619: 'Sample.M2()' は旧形式です ('このメソッドは廃止されました')
}
}
Imports System
Class Sample
' Obsolete属性を付与してこのメンバの参照を警告として通知する
<Obsolete("このメソッドの使用は非推奨です")> _
Shared Sub M1()
End Sub
' Obsolete属性を付与してこのメンバの参照をエラーとして通知する
<Obsolete("このメソッドは廃止されました", True)> _
Shared Sub M2()
End Sub
Shared Sub Main()
' Obsolete属性により、メンバの参照で警告が発生する
M1() ' warning BC40000: 'Public Shared Sub M1()' は廃止されています: 'このメソッドの使用は非推奨です'。
' Obsolete属性により、メンバの参照でエラーが発生する
M2() ' error BC30668: 'Public Shared Sub M2()' は廃止されています: 'このメソッドは廃止されました'。
End Sub
End Class
ここでは、Obsolete属性の用途と使用例、Obsolete属性で通知できる情報等について解説します。
Obsolete属性の用途と効果
クラスライブラリの利用者側
.NETのクラスライブラリやサードパーティ製クラスライブラリの利用者側では、Obsolete属性の存在によってライブラリの型やメンバが廃止予定あるいは非推奨であることを知ることができます。 Obsolete属性が付与されたクラス・メソッドを呼びだそうとしている場合、コンパイラは自動的にObsolete属性の存在を検知し、警告あるいはエラーとして利用者に通知します。
例えば、ファイル名に使用できない文字のリストを取得しようとしてPath.InvalidPathCharsフィールドを参照した場合、コンパイラは次のような警告を通知します。
using System;
using System.IO;
class Sample {
static void Main()
{
var filename = "foo*bar?.txt";
// ファイル名に使用できない文字をアンダーバーに置き換える
foreach (var c in Path.InvalidPathChars) { // warning CS0618: 'System.IO.Path.InvalidPathChars' は古い形式です: 'Please use GetInvalidPathChars or GetInvalidFileNameChars instead.'
filename = filename.Replace(c, '_');
}
Console.WriteLine(filename);
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Dim filename As String = "foo*bar?.txt"
' ファイル名に使用できない文字をアンダーバーに置き換える
For Each c As Char In Path.InvalidPathChars ' warning BC40000: 'InvalidPathChars' は旧形式です: 'Please use GetInvalidPathChars or GetInvalidFileNameChars instead.'
filename = filename.Replace(c, "_"c)
Next
Console.WriteLine(filename)
End Sub
End Class
このように、利用者はコンパイル時の警告によって機能が廃止予定・非推奨であることを知ることができます。 このとき、警告として通知されるメッセージに代替機能についての情報が含まれていれば、それも得ることができます。
Obsolete属性による警告がない場合、別途ドキュメントを参照するなどしない限り機能が廃止予定・非推奨となっているかどうかを把握することはできませんが、Obsolete属性が付与されていれば警告あるいはエラーとしてそれを知ることができます。
先の例における警告メッセージでは、Path.InvalidPathCharsの代替としてPath.GetInvalidFileNameCharsメソッドが提示されています。 利用者は、これに従うことで非推奨となっている機能を使用しないコードに書き換えることができます。
using System;
using System.IO;
class Sample {
static void Main()
{
var filename = "foo*bar?.txt";
// ファイル名に使用できない文字をアンダーバーに置き換える
// (非推奨なPath.InvalidPathCharsの代わりにPath.GetInvalidFileNameChars()を用いる)
foreach (var c in Path.GetInvalidFileNameChars()) {
filename = filename.Replace(c, '_');
}
Console.WriteLine(filename);
}
}
Imports System
Imports System.IO
Class Sample
Shared Sub Main()
Dim filename As String = "foo*bar?.txt"
' ファイル名に使用できない文字をアンダーバーに置き換える
' (非推奨なPath.InvalidPathCharsの代わりにPath.GetInvalidFileNameChars()を用いる)
For Each c As Char In Path.GetInvalidFileNameChars()
filename = filename.Replace(c, "_"c)
Next
Console.WriteLine(filename)
End Sub
End Class
このように、廃止予定・非推奨となっていることを知らずに利用しようとしていた場合でも、Obsolete属性によってそれをコンパイル時までに知ることができ、推奨される代替機能へ移行することができます。
GetInvalidFileNameCharsメソッド、ファイル名・パスに使用できない文字に関してはパスの操作 §.パスやファイル名に使用できない文字の取得 (GetInvalidPathChars/GetInvalidFileNameChars)で解説しています。
クラスライブラリの提供者側
クラスライブラリの提供者側では、バージョンアップに際して既存の機能を廃止したり、非推奨としたい場合、より洗練された実装への移行を促したい場合にObsolete属性を用いることができます。
既存の型やメンバに対してObsolete属性を付与することにより、それが廃止予定・非推奨であることを利用者側に通知することができます。 このライブラリを利用する側では、Obsolete属性が付与された型やメンバを使用しようとした場合、コンパイラが警告(またはエラー)を発します。
/* クラスライブラリのコード */
using System;
namespace ClassLibrary {
public static class StringUtils {
// 文字列strに含まれる部分文字列substrの数を返すメソッド
[Obsolete] // <-- Obsolete属性を付与することで廃止予定であることを通知する
public static int Count(string str, string substr)
{
/* 例示のため実装は省略 */
return 0;
}
}
}
' クラスライブラリのコード
Imports System
Namespace ClassLibrary
Public Module StringUtils
' 文字列strに含まれる部分文字列substrの数を返すメソッド
' (Obsolete属性を付与することで廃止予定であることを通知する)
<Obsolete> _
Public Function Count(ByVal str As String, ByVal substr As String) As Integer
' 例示のため実装は省略
Return 0
End Function
End Module
End Namespace
/* ライブラリ利用者のコード */
using System;
using ClassLibrary;
class Sample {
static void Main()
{
string text = "foobarbazfoobarbaz";
// ライブラリのメソッドを使って文字列textに含まれる"foo"の数を計上したい
Console.WriteLine(
StringUtils.Count(text, "foo") // warning CS0612: 'ClassLibrary.StringUtils.Count(string, string)' は古い形式です。
);
}
}
' ライブラリ利用者のコード
Imports System
Imports ClassLibrary
Class Sample
Shared Sub Main()
Dim text As String = "foobarbazfoobarbaz"
' ライブラリのメソッドを使って文字列textに含まれる"foo"の数を計上したい
Console.WriteLine(StringUtils.Count(text, "foo")) ' warning BC40008: 'Public Function Count(str As String, substr As String) As Integer' は旧形式です。
End Sub
End Class
Obsolete属性では、引数messageで警告として出力するメッセージ(文字列)を指定することもできるため、単なる廃止予定の通知だけでなく、廃止予定とする経緯や理由、代替となる機能など、Obsolete属性の付与に関する具体的な説明も同時に通知することができます。
/* クラスライブラリのコード */
using System;
namespace ClassLibrary {
public static class EnumUtils {
// 文字列strから対応するEnum値への変換を試みるメソッド
[Obsolete(".NET Framework 4以降ではSystem.Enum.TryParse()が提供されています。 System.Enum.TryParse()を使ってください。")]
public static bool TryParse<TEnum>(string str, out TEnum result) where TEnum : struct
{
/* 例示のため実装は省略 */
result = default(TEnum);
return false;
}
}
}
' クラスライブラリのコード
Imports System
Namespace ClassLibrary
Public Module EnumUtils
' 文字列strから対応するEnum値への変換を試みるメソッド
<Obsolete(".NET Framework 4以降ではSystem.Enum.TryParse()が提供されています。 System.Enum.TryParse()を使ってください。")> _
Public Function TryParse(Of TEnum As Structure)(ByVal str As String, ByRef result As TEnum) As Boolean
' 例示のため実装は省略
result = Nothing
Return False
End Function
End Module
End Namespace
/* ライブラリ利用者のコード */
using System;
using ClassLibrary;
class Sample {
static void Main()
{
var str = "Sunday";
DayOfWeek d;
// ここで警告が出力される
if (EnumUtils.TryParse(str, out d)) // warning CS0618: 'ClassLibrary.EnumUtils.TryParse<TEnum>(string, out TEnum)' は古い形式です: '.NET Framework 4以降ではSystem.Enum.TryParse()が提供されています。 System.Enum.TryParse()を使ってください。'
Console.WriteLine(d);
// この警告メッセージに従って、利用者は代替機能を使ったコードに書き換えることができる
if (Enum.TryParse(str, out d))
Console.WriteLine(d);
}
}
' ライブラリ利用者のコード
Imports System
Imports ClassLibrary
Class Sample
Shared Sub Main()
Dim str As String = "Sunday"
Dim d As DayOfWeek
' ここで警告が出力される
If EnumUtils.TryParse(str, d) Then ' warning BC40000: 'Public Function TryParse(Of TEnum As Structure)(str As String, ByRef result As TEnum) As Boolean' は旧形式です: '.NET Framework 4以降ではSystem.Enum.TryParse()が提供されています。 System.Enum.TryParse()を使ってください。'
Console.WriteLine(d)
End If
' この警告メッセージに従って、利用者は代替機能を使ったコードに書き換えることができる
If [Enum].TryParse(str, d) Then
Console.WriteLine(d)
End If
End Sub
End Class
また、Obsolete属性の引数errorにtrue
を指定すれば、Obsoleteとなったメンバの使用を警告ではなくエラーとして通知させるようにすることができます。
利用者側では、このような型やメンバを使用しようとするとコンパイルエラーとなり、通常の手段では一切使用することができなくなります。 機能の利用には問題があり、機能へのアクセスを一切禁止したいような場合には、Obsolete属性によってコンパイルエラーとすることができます。
/* クラスライブラリのコード */
using System;
namespace ClassLibrary {
// POPを使ってメールサーバーにアクセスするクライアント
public class PopClient {
// APOPによるログインを試行するメソッド
[Obsolete(error: true, message: "APOPによるログインには脆弱性が指摘されたため、廃止しました。 代わりにPopClient.LoginSaslMD5()などを使用してください。 (詳細: http://www.ipa.go.jp/security/vuln/200704_APOP.html)")]
public void LoginApop(string user, string pass)
{
// 仮に呼び出されたとしても、本来の動作は行わず、例外をスローする
throw new NotSupportedException("APOPによるログインは廃止されたため、サポートされません");
}
// SASL MD5を使ったログインを試行するメソッド
public void LoginSaslMD5(string user, string pass)
{
/* 例示のため実装は省略 */
}
}
}
' クラスライブラリのコード
Imports System
Namespace ClassLibrary
' POPを使ってメールサーバーにアクセスするクライアント
Public Class PopClient
' APOPによるログインを試行するメソッド
<Obsolete("APOPによるログインには脆弱性が指摘されたため、廃止しました。 代わりにPopClient.LoginSaslMD5()などを使用してください。 (詳細: http://www.ipa.go.jp/security/vuln/200704_APOP.html)", True)> _
Public Sub LoginApop(ByVal user As String, ByVal pass As String)
' 仮に呼び出されたとしても、本来の動作は行わず、例外をスローする
Throw New NotSupportedException("APOPによるログインは廃止されたため、サポートされません")
End Sub
' SASL MD5を使ったログインを試行するメソッド
Public Sub LoginSaslMD5(ByVal user As String, ByVal pass As String)
' 例示のため実装は省略
End Sub
End Class
End Namespace
/* ライブラリ利用者のコード */
using System;
using ClassLibrary;
class Sample {
static void Main()
{
var c = new PopClient();
// この呼び出しはエラーとなり、コンパイルに失敗する
c.LoginApop("user", "pass"); // error CS0619: 'ClassLibrary.PopClient.LoginApop(string, string)' は古い形式です: 'APOPによるログインには脆弱性が指摘されたため、廃止しました。 代わりにPopClient.LoginSaslMD5()などを使用してください。 (詳細: http://www.ipa.go.jp/security/vuln/200704_APOP.html)'
// 代わりに提示された代替機能を使用する
c.LoginSaslMD5("user", "pass");
}
}
' ライブラリ利用者のコード
Imports System
Imports ClassLibrary
Class Sample
Shared Sub Main()
Dim c As New PopClient()
' この呼び出しはエラーとなり、コンパイルに失敗する
c.LoginApop("user", "pass") ' error BC30668: 'Public Sub LoginApop(user As String, pass As String)' は旧形式です: 'APOPによるログインには脆弱性が指摘されたため、廃止しました。 代わりにPopClient.LoginSaslMD5()などを使用してください。 (詳細: http://www.ipa.go.jp/security/vuln/200704_APOP.html)'
// 代わりに提示された代替機能を使用する
c.LoginSaslMD5("user", "pass")
End Sub
End Class
公開せざるを得ない機能の利用に対する警告としての使用
このほかにも、ライブラリ実装の都合あるいは実装の経緯上・互換性維持上の理由など、ライブラリ外に公開せざるを得ない(public
な)型やメンバが存在する場合にもObsolete属性を用いることができます。
例えば、(Path.InvalidPathCharsのように)過去のバージョンで公開していた機能を安全な形に隠蔽するようにした場合や、デザイナや外部ツール、テストコードなどから参照されることが主目的の、利用者の直接使用を意図しない機能などが考えられますが、ドキュメント等で記載されない型やメンバであっても、それがpublic
であれば入力候補等で表示される場合があります。
このようなメンバにObsolete属性を付与しておくことにより、利用者が誤って使用してしまうことや、仮に意図的に使用した場合でもその結果は保証できないことなどを通知することができます。
using System;
namespace ClassLibrary {
public static class Infrastructure {
// 実装上の都合でpublicとせざるを得ないメソッド
[Obsolete("ライブラリの初期化に必要なメソッドです。 ライブラリ外から呼び出した場合は現在の状態が変更されるため、正常な動作が保証できません。 呼び出さないでください。")]
public static void Initialize()
{
}
// 実装上の都合でpublicとせざるを得ないプロパティ
[Obsolete("ライブラリの状態取得に必要なプロパティです。 このプロパティの情報を前提としたコードを記述しないでください。")]
public static bool IsInitialized {
get { return true; }
}
}
}
Imports System
Namespace ClassLibrary
Public Module Infrastructure
' 実装上の都合でPublicとせざるを得ないメソッド
<Obsolete("ライブラリの初期化に必要なメソッドです。 ライブラリ外から呼び出した場合は現在の状態が変更されるため、正常な動作が保証できません。 呼び出さないでください。")> _
Public Sub Initialize()
End Sub
' 実装上の都合でPublicとせざるを得ないプロパティ
<Obsolete("ライブラリの状態取得に必要なプロパティです。 このプロパティの情報を前提としたコードを記述しないでください。")> _
Public ReadOnly Property IsInitialized As Boolean
Get
Return True
End Get
End Property
End Module
End Namespace
.NETのクラスライブラリにおいてもこのようなメンバが存在します。 例えばWebClient.AllowReadStreamBufferingプロパティなどはObsolete属性が付与されていて、ユーザーが直接使用することを想定としたものではないことが通知されるようになっています。
Obsolete属性によってこういったメンバーに対する呼び出しをエラーとなるようにし、直接呼び出せないようにした場合でも、リフレクションによって呼び出すことができます。
メンバの存在をデバッガやデザイナから見えなくする目的には、DebuggerHidden属性やDebuggerNonUserCode属性、EditorBrowsable属性を用いることができます。 DebuggerHiddenAttribute属性・DebuggerNonUserCode属性について詳しくはデバッグ操作と属性を参照してください、
簡易なコード解析としての使用
Obsolete属性本来の目的とは異なりますが、IDEによるコード解析機能が使えない場合に、メンバを参照している箇所をコンパイラに検出させる用途でも使うことができます。 例えば、次のようにメソッドにObsolete属性をつけることによって、そのメソッドを呼び出している箇所を警告として表示させることができます。
using System;
class Sample {
[Obsolete("ここでメソッドを使用している")]
static void Test() {}
static void Main()
{
Test(); // warning CS0618: 'Sample.Test()' は古い形式です: 'ここでメソッドを使用している'
}
}
Imports System
Class Sample
<Obsolete("ここでメソッドを使用している")> _
Shared Sub Test()
End Sub
Shared Sub Main()
Test() ' warning BC40000: 'Public Shared Sub Test()' は旧形式です: 'ここでメソッドを使用している'
End Sub
End Class
ユーザー定義の警告・エラーを表示したい場合は、#warningディレクティブや#errorディレクティブを用いることができます。
警告ID・エラーIDの割り当て (DiagnosticId)
.NET 5以降では、Obsolete属性にDiagnosticIdプロパティが追加されています。 このプロパティはObsolete属性に診断用の固有なIDを割り当てるもので、コンパイル時にはこのIDが警告・エラー番号としても表示されます。 言い換えると、独自のコンパイル警告・エラーのIDを定義して個々のObsolete属性に割り当てるものと見ることもできます。
using System;
class Sample {
// Obsolete属性でDiagnosticIdプロパティにIDを割り当てる
[Obsolete("このメソッドは非推奨です", DiagnosticId = "WARN_FOO")]
static void Foo() {}
// 警告・エラー番号の代わりに、DiagnosticIdプロパティに指定したIDが使用される
static void Main() => Foo(); // warning WARN_FOO: 'Sample.Foo()' は旧形式です ('このメソッドは非推奨です')
}
Imports System
Class Sample
' Obsolete属性でDiagnosticIdプロパティにIDを割り当てる
<Obsolete("このメソッドは非推奨です", DiagnosticId := "WARN_FOO")> _
Shared Sub Foo()
End Sub
Shared Sub Main()
' 警告・エラー番号の代わりに、DiagnosticIdプロパティに指定したIDが使用される
Foo() ' warning WARN_FOO: 'Public Shared Sub Foo()' は廃止されています: 'このメソッドは非推奨です'。
End Sub
End Class
通常、Obsolete属性によるコンパイル時警告はCS0618
(C#)/BC40000
(VB)、コンパイルエラーはCS0619
(C#)/BC30668
(VB)として通知されますが、DiagnosticIdプロパティによってIDが割り当てられている場合は、そのIDがコンパイル時に警告・エラーのIDとして表示されます。
このプロパティは、Obsolete属性が付与された理由や経緯、対象の機能による分類など、任意に定めたルールに基づくIDを割り当てる目的に使用することができます。
例えば「脆弱性の発見によるAPIの廃止」に該当するObsolete属性にはVULN01
、「より洗練されたAPIの追加に伴う非推奨化」に該当するObsolete属性にはDEPR01
のようにIDを割り当てたり、ある機能FのAPIに適用されるObsolete属性にはFEATURE-F_01
のIDを使用する、といったように、ルールを任意に定めてIDを割り当てることができます。 IDの形式は特に定められていないため、DiagnosticIdプロパティには任意の文字列を指定することができます。
.NETのクラスライブラリでは、.NET 5以降の変更で付与されたObsolete属性にIDSYSLIBxxxx
が割り当てられています。 SYSLIBxxxx
は付与された理由によって個別にIDが割り振られていて、その一覧は.NET 5 以降の古い機能 - .NET Core | Microsoft Docsにて参照することができます。 例として、「UTF-7エンコードは安全ではない」ことを理由に非推奨とされた型やメンバにはSYSLIB0001が割り当てられています。
また、DiagnosticIdプロパティでは個別の警告IDが割り当てられることになるため、コンパイルオプション-nowarn
/<NoWarn>
や#pragma warning disable/restore CSxxxx
(C#)や#Disable/Enable Warning BCxxxxx
(VB)といったディレクティブによる警告の抑止を、Obsolete属性による警告全てではなくIDに対して個別に行うことができるようになります。
class Sample {
[Obsolete("このメソッドは非推奨です", DiagnosticId = "WARN_FOO")]
static void Foo() {}
[Obsolete("このメソッドは非推奨です", DiagnosticId = "WARN_BAR")]
static void Bar() {}
static void Main()
{
// 以下、警告WARN_BARの出力を抑止する
#pragma warning disable WARN_BAR
Foo(); // warning WARN_FOO: 'Sample.Foo()' は旧形式です ('このメソッドは非推奨です')
Bar(); // ここでは警告WARN_BARが出力されない
// 以下、警告WARN_BARの出力抑止を回復する
#pragma warning restore WARN_BAR
Bar(); // warning WARN_BAR: 'Sample.Bar()' は旧形式です ('このメソッドは非推奨です')
}
}
Imports System
Class Sample
<Obsolete("このメソッドは非推奨です", DiagnosticId := "WARN_FOO")> _
Shared Sub Foo()
End Sub
<Obsolete("このメソッドは非推奨です", DiagnosticId := "WARN_BAR")> _
Shared Sub Bar()
End Sub
Shared Sub Main()
' 以下、警告WARN_BARの出力を抑止する
#Disable Warning WARN_BAR
Foo() ' warning WARN_FOO: 'Public Shared Sub Foo()' は廃止されています: 'このメソッドは非推奨です'。
Bar() ' ここでは警告WARN_BARが出力されない
' 以下、警告WARN_BARの出力抑止を回復する
#Enable Warning WARN_BAR
Bar() ' warning WARN_BAR: 'Public Shared Sub Bar()' は廃止されています: 'このメソッドは非推奨です'。
End Sub
End Class
このほか、UrlFormatプロパティと組み合わせて使用することにより、プレースホルダが設定されたURLにこのIDを展開し、理由や経緯を説明するドキュメントへのリンクとして提示することもできます。
URLの埋め込み (UrlFormat)
.NET 5以降では、Obsolete属性にUrlFormatプロパティが追加されています。 このプロパティは、IDE上やコード解析において何らかのドキュメントへのリンクとして使われることが想定されていて、Obsolete属性が付与された理由や経緯などの詳細や、具体的な回避策および代替手段を提示するドキュメントのURLを指定します。
名前がFormatとなっていることから示されるように、URLにはプレースホルダを含めることができ、DiagnosticIdプロパティの値を展開したURLを得ることができるようになっています。 例えばUrlFormatにhttps://example.net/api/doc-{0}.html
を設定すると、DiagnosticIdがDEPR01
ならURLhttps://example.net/api/doc-DEPR01.html
として展開されます。 このため、短縮URLやクエリ付きのURLなど、特定の形式に従ったURLを設定することができます。
ただし、DiagnosticIdプロパティとは異なり、(少なくとも.NET 5時点の)dotnet build
によるコマンドラインのビルドや、(少なくともVersion 16.8.1時点の)Visual Studioでは、UrlFormatプロパティに指定されたURLは警告・エラーメッセージ内には含まれないようです。
using System;
class Sample {
// UrlFormatに指定する共通のURL
const string obsoleteUrlFormat = "https://example.com/api/doc-{0}.html";
// Obsolete属性にDiagnosticId, UrlFormatを割り当てる
[Obsolete("このメソッドは非推奨です", DiagnosticId = "WARN_FOO", UrlFormat = obsoleteUrlFormat)]
static void Foo() {}
[Obsolete("このメソッドは廃止されました", error: true, DiagnosticId = "ERROR_BAR", UrlFormat = obsoleteUrlFormat)]
static void Bar() {}
static void Main()
{
Foo(); // warning WARN_FOO: 'Sample.Foo()' は旧形式です ('このメソッドは非推奨です')
Bar(); // error ERROR_BAR: 'Sample.Bar()' は旧形式です ('このメソッドは廃止されました')
}
}
Imports System
Class Sample
' UrlFormatに指定する共通のURL
Const obsoleteUrlFormat As String = "https://example.com/api/doc-{0}.html"
' Obsolete属性にDiagnosticId, UrlFormatを割り当てる
<Obsolete("このメソッドは非推奨です", DiagnosticId := "WARN_FOO", UrlFormat := obsoleteUrlFormat)> _
Shared Sub Foo()
End Sub
<Obsolete("このメソッドは廃止されました", True, DiagnosticId := "WARN_BAR", UrlFormat := obsoleteUrlFormat)> _
Shared Sub Bar()
End Sub
Shared Sub Main()
Foo() ' warning WARN_FOO: 'Public Shared Sub Foo()' は廃止されています: 'このメソッドは非推奨です'。
Bar() ' error WARN_BAR: 'Public Shared Sub Bar()' は廃止されています: 'このメソッドは廃止されました'。
End Sub
End Class
Roslyn(Microsoft.CodeAnalysis)によるコード解析では、DiagnosticDescriptor.HelpLinkUriプロパティよりDiagnosticIdが展開されたURLを取得することができます。 このため、将来的にはIDEやコマンドラインでも展開されたUrlFormatが表示されるようになる可能性が考えられます。
using System;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp; // `dotnet add sample.csproj package Microsoft.CodeAnalysis.CSharp`
const string sourceFile = "test.cs";
var tree = CSharpSyntaxTree.ParseText(
text: File.ReadAllText(sourceFile),
options: new CSharpParseOptions(
languageVersion: LanguageVersion.Default,
documentationMode: DocumentationMode.Parse,
kind: SourceCodeKind.Regular
)
);
var compilation = CSharpCompilation
.Create(assemblyName: "test")
.AddReferences(MetadataReference.CreateFromFile(typeof(string).Assembly.Location))
.WithOptions(
new CSharpCompilationOptions(
outputKind: OutputKind.ConsoleApplication
)
)
.AddSyntaxTrees(tree);
foreach (var d in compilation
.GetDiagnostics()
.Where(d => d.Severity == DiagnosticSeverity.Warning || d.Severity == DiagnosticSeverity.Error)
) {
Console.WriteLine("{0}, see {1}", d, d.Descriptor.HelpLinkUri);
}
入力ソースtest.cs
として先の例のコードを使用した場合。
(16,5): warning WARN_FOO: 'Sample.Foo()' は旧形式です ('このメソッドは非推奨です'), see https://example.com/api/doc-WARN_FOO.html (17,5): error ERROR_BAR: 'Sample.Bar()' は旧形式です ('このメソッドは廃止されました'), see https://example.com/api/doc-ERROR_BAR.html
Option Infer On
Imports System
Imports System.IO
Imports System.Linq
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.VisualBasic ' `dotnet add sample.vbproj package Microsoft.CodeAnalysis.VisualBasic`
Class Sample
Shared Sub Main()
Const sourceFile As String = "test.vb"
Dim tree = VisualBasicSyntaxTree.ParseText(
text := File.ReadAllText(sourceFile),
options := New VisualBasicParseOptions(
languageVersion := LanguageVersion.Default,
documentationMode := DocumentationMode.Parse,
kind := SourceCodeKind.Regular
)
)
Dim compilation = VisualBasicCompilation.
Create(assemblyName := "test").
AddReferences(MetadataReference.CreateFromFile(GetType(String).Assembly.Location)).
WithOptions(
New VisualBasicCompilationOptions(
outputKind := OutputKind.ConsoleApplication
)
).
AddSyntaxTrees(tree)
For Each d In compilation.
GetDiagnostics().
Where(Function (_d) _d.Severity = DiagnosticSeverity.Warning OrElse _d.Severity = DiagnosticSeverity.Error)
Console.WriteLine("{0}, see {1}", d, d.Descriptor.HelpLinkUri)
Next
End Sub
End Class
入力ソースtest.vb
として先の例のコードを使用した場合。
(17,5): warning WARN_FOO: 'Public Shared Sub Foo()' は廃止されています: 'このメソッドは非推奨です'。, see https://example.com/api/doc-WARN_FOO.html (18,5): error WARN_BAR: 'Public Shared Sub Bar()' は廃止されています: 'このメソッドは廃止されました'。, see https://example.com/api/doc-WARN_BAR.html
Obsolete属性を適用できる個所
Obsolete属性は、次のようにクラス・構造体・インターフェイスなどのすべての型、およびメソッド・プロパティ(アクセサ含む)・イベント・フィールドなどのメンバに対して適用することができます。 また、public
ではない型・メンバにも適用することができます。 アセンブリ全体をObsoleteにすることはできません。
using System;
// アセンブリ全体には適用できない
//[assembly: Obsolete]
namespace ClassLibrary {
[Obsolete] public struct S { }
[Obsolete] public interface I {}
[Obsolete] public class C {
[Obsolete] void Method() {}
[Obsolete] bool Property {
[Obsolete] get;
[Obsolete] set;
}
[Obsolete] event EventHandler Event;
[Obsolete] int Field;
}
}
Imports System
' アセンブリ全体には適用できない
'<Assembly: Obsolete>
Namespace ClassLibrary
<Obsolete> _
Public Structure S
End Structure
<Obsolete> _
Public Interface I
End Interface
<Obsolete> _
Public Class C
<Obsolete> _
Sub Method()
End Sub
<Obsolete> _
Property P As Boolean
<Obsolete> _
Get
Return False
End Get
<Obsolete> _
Set
End Set
End Property
<Obsolete> _
Event E As EventHandler
<Obsolete> _
Dim Filed As Integer
End Class
End Namespace
Obsolete属性が付与された型のメンバ
Obsoleteな型のObsoleteではない静的メンバ(static
/Shared
)の参照では、型の参照がObsoleteとなるため、静的メンバが実際にObsoleteであるか否かに関わらず、常にObsoleteであるかのような状態になります。 一方、Obsoleteな型のObsoleteでないメンバでは、インスタンスを介した参照はObsoleteとはなりません。
using System;
// 型をObsoleteにする
[Obsolete]
class C {
public void M1() {}
[Obsolete]
public void M2() {}
public static void M3() {}
}
class Sample {
static void Main()
{
// Obsoleteな型なので当然警告となる
var c = new C(); // warning CS0612: 'C' は古い形式です
// Obsoleteな型のObsoleteでないメンバの参照は、警告とはならない
c.M1();
// Obsoleteなメンバの参照は、当然警告となる
c.M2(); // warning CS0612: 'C.M2()' は古い形式です
// Obsoleteな型のObsoleteでない静的メンバの参照は、型の参照で警告となる
C.M3(); // warning CS0612: 'C' は古い形式です
}
}
Imports System
' 型をObsoleteにする
<Obsolete> _
Class C
Public Sub M1()
End Sub
<Obsolete> _
Public Sub M2()
End Sub
Public Shared Sub M3()
End Sub
End Class
Class Sample
Shared Sub Main()
' Obsoleteな型なので当然警告となる
Dim c As New C() ' warning BC40008: 'C' は廃止されています。
' Obsoleteな型のObsoleteでないメンバの参照は、警告とはならない
c.M1()
' Obsoleteなメンバの参照は、当然警告となる
c.M2() ' warning BC40008: 'Public Sub M2()' は廃止されています。
' Obsoleteな型の静的メンバの参照は、型の参照で警告となる
C.M3() ' warning BC40008: 'C' は廃止されています。
End Sub
End Class
リフレクションによるObsoleteなメンバの呼び出し
仮に型やメンバにObsolete属性が指定されている場合でも、リフレクションによって呼び出す場合は警告やエラーは一切出力されません。 次の例ではエラーとなるObsolete属性を指定したメソッドを用意し、それをリフレクションによって呼び出しています。
using System;
using System.Reflection;
class Sample {
[Obsolete("呼び出しがエラーとなるメソッド", error: true)]
public static void M()
{
Console.WriteLine("このメソッドの呼び出しはコンパイルエラーになりますが、リフレクションによる呼び出しは成功します。");
}
static void Main()
{
// リフレクションによって上記のObsoleteなメソッドを呼び出す
// (コンパイルエラーにならず、例外もスローせずに呼び出せてしまう)
typeof(Sample).InvokeMember("M", BindingFlags.InvokeMethod, null, null, null);
}
}
Imports System
Imports System.Reflection
Class Sample
<Obsolete("呼び出しがエラーとなるメソッド", True)> _
Public Shared Sub M()
Console.WriteLine("このメソッドの呼び出しはコンパイルエラーになりますが、リフレクションによる呼び出しは成功します。")
End Sub
Shared Sub Main()
' リフレクションによって上記のObsoleteなメソッドを呼び出す
' (コンパイルエラーにならず、例外もスローせずに呼び出せてしまう)
GetType(Sample).InvokeMember("M", BindingFlags.InvokeMethod, Nothing, Nothing, Nothing)
End Sub
End Class
このメソッドの呼び出しはコンパイルエラーになりますが、リフレクションによる呼び出しは成功します。
上記のように、エラーとなるObsolete属性が指定されているメソッドでも、リフレクションを使って呼び出す場合はコンパイルエラーにはなりません。 また、呼び出し時にメンバがObsoleteであることを理由とした例外がスローされることもありません。
これを言い換えると、リフレクションによってメソッド呼び出し等を行っている箇所は、仮にそのメソッドがObsoleteになっている・なった場合でもコンパイル時には検知することができないということになります。 逆に、Obsolete属性によって直接の呼び出しがコンパイルエラーとなる場合でも、リフレクションによってそれを回避して呼び出すことができるとも言えます。
いずれにしても、リフレクションを用いる場合はこのようなリスクとメリットを考慮した上で行う必要があります。
Obsolete属性とXMLドキュメントコメント
C#やVB.NETでは、XMLドキュメントコメントによってメソッドの機能・動作・引数と戻り値の説明などを記述することができます。 しかし、Javadocにおける@deprecated
タグのような、廃止予定・非推奨となった旨を記述する要素は定義されていません。 具体的には<obsolete>
や<deprecated>
のような要素は存在しません。
そのため、廃止予定・非推奨に関する記述は<summary>
要素や<remarks>
要素として記述するか、UrlFormatプロパティのURLとしてリンクされる個別のドキュメントとして記述する必要があります。
なお、Javadoc コメントと等価な XML ドキュメントおよび<deprecated> (JavaScript)によれば、J#では<obsolete>
要素、JavaScriptでは<deprecated>
要素が定義されています。 C#やVB.NETでも独自の要素として<deprecated>
要素を使った記述をすること自体は行えますが、公式には定義されていない要素となるため、ビューアーやドキュメントジェネレータが対応していない限りは未定義要素として表示されるか、あるいは単に無視されることになります。
この他、XMLドキュメントコメントで用いることができる要素と意味についての詳細はXMLドキュメントコメントを用いたドキュメントの作成 §.XMLドキュメントコメントの要素を参照してください。
.NETクラスライブラリの非推奨な型・メンバ
.NETのクラスライブラリでは、BinaryFormatter・SoapFormatterといったクラスや、Uri.MakeRelativeメソッドやPath.InvalidPathCharsフィールドといったメンバなどにObsolete属性が適用され、非推奨となっています。
このほかにも、ドキュメントの以下のページにて非推奨となった型・メンバの一覧が掲載されています。
- .NET 5 以降の古い機能 - .NET Core | Microsoft Docs
-
破壊的変更の種類 - .NET Core | Microsoft Docs
- 互換性に影響する変更 - .NET Core | Microsoft Docs (.NET Frameworkから.NET Core、.NET Core 2.2から3.1など)
- .NET Framework で互換性のために残されている機能
公開されたAPIの廃止とObsolete属性
ここでは、クラスライブラリで一度提供を開始したメソッドをObsoleteとし、最終的に廃止するまでをリリース毎に考えていきます。 機能の廃止や置き換えに関するリリースポリシーの一例としてご覧ください。
バージョン1.0 (APIの提供開始)
まず、最初のリリースでは以下のようにメソッドを提供したとします。
[assembly: System.Reflection.AssemblyVersion("1.0")]
using System;
namespace ClassLibrary {
public static class StringUtils {
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <returns><paramref name="str"/>の文字の並びを逆転した<see cref="System.String"/>を返します。</returns>
public static string Reverse(string str)
{
/* 例示のため実装は省略 */
return string.Empty;
}
}
}
バージョン2.0 (APIの非推奨化)
実装に問題がある、より洗練された実装を提供するなどの理由により、今後のリリースではこのメソッドの利用を推奨しないことになったとします。 これに従い、メソッドにObsolete属性を追加することによって利用者にその旨を通知します。 同時に、代替となる新しいメソッドも用意します。
[assembly: System.Reflection.AssemblyVersion("2.0")]
using System;
namespace ClassLibrary {
public static class StringUtils {
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <remarks>このメソッドはサロゲートペアを含む文字列を正しく処理できません。</remarks>
/// <returns><paramref name="str"/>の文字の並びを逆転した<see cref="System.String"/>を返します。</returns>
[Obsolete("このメソッドは推奨されません。 文字列がサロゲートペアを含む場合はCodePointWiseReverse()を使用してください。")]
public static string Reverse(string str)
{
/* 例示のため実装は省略 */
return string.Empty;
}
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <returns><paramref name="str"/>の文字の並びを逆転した<see cref="System.String"/>を返します。</returns>
/// <remarks>このメソッドはサロゲートペアを含む文字列でも正しく処理します。</remarks>
public static string CodePointWiseReverse(string str)
{
/* 例示のため実装は省略 */
return string.Empty;
}
}
}
このとき、旧式メソッドと動作が変わらない代替メソッドを提供できる場合や、動作が変わっても問題ないと判断する場合は、旧式メソッドから代替メソッドを呼び出すようにして機能を完全に置き換えてしまうこともできます。
[assembly: System.Reflection.AssemblyVersion("2.1")]
using System;
namespace ClassLibrary {
public static class StringUtils {
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <returns><paramref name="str"/>の文字の並びを逆転した<see cref="System.String"/>を返します。</returns>
[Obsolete("このメソッドは廃止予定です。 CodePointWiseReverse()を使用してください。")]
public static string Reverse(string str)
{
// 代替メソッドを使用する実装にする
return CodePointWiseReverse(str);
}
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <returns><paramref name="str"/>の文字の並びを逆転した<see cref="System.String"/>を返します。</returns>
/// <remarks>このメソッドはサロゲートペアを含む文字列でも正しく処理します。</remarks>
public static string CodePointWiseReverse(string str)
{
/* 例示のため実装は省略 */
return string.Empty;
}
}
}
バージョン3.0 (APIの廃止)
次のリリースでは、これまでに廃止予定としたメソッドを廃止することになったとします。 これに従いObsolete属性のメッセージを変更し、またこれまで警告としていたメソッドの利用を、error = true
を追加してエラーとして扱うように変更します。
[assembly: System.Reflection.AssemblyVersion("3.0")]
using System;
namespace ClassLibrary {
public static class StringUtils {
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <remarks>このメソッドは廃止されました。 CodePointWiseReverse()を使用してください。</remarks>
/// <returns><paramref name="str"/>の文字の並びを逆転した<see cref="System.String"/>を返します。</returns>
[Obsolete(error: true, message: "このメソッドは廃止されました。 CodePointWiseReverse()を使用してください。")]
public static string Reverse(string str)
{
/* 例示のため実装は省略 */
return string.Empty;
}
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <returns><paramref name="str"/>の文字の並びを逆転した<see cref="System.String"/>を返します。</returns>
/// <remarks>このメソッドはサロゲートペアを含む文字列でも正しく処理します。</remarks>
public static string CodePointWiseReverse(string str)
{
/* 例示のため実装は省略 */
return string.Empty;
}
}
}
この時点で、利用者は旧式のメソッドを使うことができなくなり、代替メソッドを使用しなければならなくなります。
Obsolete属性による警告・エラーは、コンパイル時にのみ発せられます。 従って、ライブラリを参照するコンパイル済みのコードがある場合、ライブラリ側でObsolete属性が警告からエラーに変わったとしても、コンパイル済みコードの実行には影響しません。 再コンパイルする時点で初めてエラーとして通知されます。
実行可能ファイルを再コンパイルせず、コンパイル済みのライブラリだけを置き換えてバージョンアップするようなシナリオの場合、Obsolete属性のerrorパラメータがfalse
からtrue
に変わっても、引き続き旧式のメソッドが呼び出されることになります。
旧式メソッドの廃止に伴い、実装も削除すると判断した場合は、次のように例外NotSupportedExceptionをスローするようにすることもできます。
[assembly: System.Reflection.AssemblyVersion("3.1")]
using System;
namespace ClassLibrary {
public static class StringUtils {
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <remarks>このメソッドは廃止されました。 CodePointWiseReverse()を使用してください。</remarks>
/// <exception cref="NotSupportedException">このメソッドを呼び出した場合にスローされます。</exception>
[Obsolete(error: true, message: "このメソッドは廃止されました。 CodePointWiseReverse()を使用してください。")]
public static string Reverse(string str)
{
throw new NotSupportedException("このメソッドは廃止されました。 CodePointWiseReverse()を使用してください。");
}
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <returns><paramref name="str"/>の文字の並びを逆転した<see cref="System.String"/>を返します。</returns>
/// <remarks>このメソッドはサロゲートペアを含む文字列でも正しく処理します。</remarks>
public static string CodePointWiseReverse(string str)
{
/* 例示のため実装は省略 */
return string.Empty;
}
}
}
errorパラメータをtrue
にした時点で、新たにコンパイルするコードではエラーとなり、利用できない状態になります。 しかし、コンパイル済みのコードからは依然として呼び出し自体は可能であるため、そういった呼び出しも禁止したい場合はこのように例外をスローするようにします。
実装されていない旨を強調するために、NotSupportedExceptionではなくNotImplementedExceptionをスローすることも考えられます。 ただ、NotImplementedExceptionでは今後実装される可能性のニュアンスを含むため、完全に廃止された機能の場合はどちらの例外がより妥当かどうかは判断が分かれます。
.NETでは、ObsoleteExceptionやDeprecatedExceptionといったようなこの状況に適した例外クラスは用意されていません。 十分な必要性があるなら、NotSupportedExceptionあるいはNotImplementedExceptionを継承してこのような例外クラスを作成し、スローするということも考えられます。
バージョン4.0 (APIの完全削除)
ここまでの時点では、機能自体は廃止したものの、クラスライブラリのAPI互換性は維持されています。
クラスライブラリのAPI互換性を破壊するようなリリースを行っても問題ないと判断する場合は、次のように完全にメソッド自体を削除した上でリリースすることもできます。
[assembly: System.Reflection.AssemblyVersion("4.0")]
using System;
namespace ClassLibrary {
public static class StringUtils {
/*
* 廃止したメソッドを完全に削除する(API互換性は失われる)
*
[Obsolete(error: true, message: "このメソッドは廃止されました。 CodePointWiseReverse()を使用してください。")]
public static string Reverse(string str)
{
throw new NotSupportedException("このメソッドは廃止されました。 CodePointWiseReverse()を使用してください。");
}
*/
/// <summary>文字列の並びを逆転した結果を返します。</summary>
/// <returns><paramref name="str"/>の文字の並びを逆転した<see cref="System.String"/>を返します。</returns>
/// <remarks>このメソッドはサロゲートペアを含む文字列でも正しく処理します。</remarks>
public static string CodePointWiseReverse(string str)
{
/* 例示のため実装は省略 */
return string.Empty;
}
}
}
ここまでの例でライブラリのバージョンを明記するために用いた属性AssemblyVersionについてはアセンブリのバージョン情報を設定・取得するなどを参照してください。