Windows Communication Foundation (WCF) では、2 つの異なるシリアル化テクノロジを使用して、アプリケーション内のデータをクライアントとサービスの間で送信される XML ( DataContractSerializer
と XmlSerializer
) に変換できます。
DataContractSerializer
既定では、WCF は DataContractSerializer クラスを使用してデータ型をシリアル化します。 このシリアライザーでは、次の型がサポートされています。
プリミティブ型 (整数、文字列、バイト配列など) だけでなく、プリミティブとして扱われる XmlElement や DateTimeなどの特殊な型もあります。
データ コントラクト型 ( DataContractAttribute 属性でマークされた型)。
ISerializable インターフェイスを実装する型を含む、SerializableAttribute属性でマークされた型。
IXmlSerializable インターフェイスを実装する型。
多くのジェネリック コレクション型を含む多くの一般的なコレクション型。
多くの .NET Framework 型は後者の 2 つのカテゴリに分類されるため、シリアル化できます。 シリアル化可能な型の配列もシリアル化可能です。 完全な一覧については、「 サービス コントラクトでのデータ転送の指定」を参照してください。
データ コントラクト型と共に使用される DataContractSerializerは、新しい WCF サービスを記述する場合に推奨される方法です。 詳細については、「 データ コントラクトの使用」を参照してください。
XmlSerializer
WCF では、 XmlSerializer クラスもサポートされています。 XmlSerializer クラスは WCF に固有ではありません。 Web サービスで使用されるのと同じシリアル化エンジン ASP.NET。 XmlSerializer クラスは、DataContractSerializer クラスよりもはるかに狭い型セットをサポートしますが、結果の XML をより細かく制御でき、XML スキーマ定義言語 (XSD) 標準の多くをサポートしています。 また、シリアル化可能な型に対する宣言属性も必要ありません。 詳細については、.NET Framework ドキュメントの XML シリアル化に関するトピックを参照してください。 XmlSerializer クラスは、データ コントラクト型をサポートしていません。
Svcutil.exe または Visual Studio の サービス参照の追加 機能を使用してサード パーティのサービスのクライアント コードを生成したり、サード パーティのスキーマにアクセスしたりする場合は、適切なシリアライザーが自動的に選択されます。 スキーマが DataContractSerializerと互換性がない場合は、 XmlSerializer が選択されます。
XmlSerializer に切り替える
場合によっては、 XmlSerializerに手動で切り替える必要があります。 これは、たとえば次のような場合に発生します。
ASP.NET Web サービスから WCF にアプリケーションを移行する場合は、新しいデータ コントラクト型を作成する代わりに、既存の XmlSerializer互換性のある型を再利用できます。
メッセージに表示される XML を正確に制御することが重要であるが、Web サービス記述言語 (WSDL) ドキュメントを使用できない場合 (たとえば、DataContractSerializer と互換性のない、標準化された公開された特定のスキーマに準拠する必要がある型を含むサービスを作成する場合)。
従来の SOAP エンコード標準に従うサービスを作成する場合。
このような場合や他の場合は、次のコードに示すように、XmlSerializerFormatAttribute
属性をサービスに適用することで、XmlSerializer クラスに手動で切り替えることができます。
[ServiceContract]
[XmlSerializerFormat]
public class BankingService
{
[OperationContract]
public void ProcessTransaction(BankingTransaction bt)
{
// Code not shown.
}
}
//BankingTransaction is not a data contract class,
//but is an XmlSerializer-compatible class instead.
public class BankingTransaction
{
[XmlAttribute]
public string Operation;
[XmlElement]
public Account fromAccount;
[XmlElement]
public Account toAccount;
[XmlElement]
public int amount;
}
//Notice that the Account class must also be XmlSerializer-compatible.
<ServiceContract(), XmlSerializerFormat()> _
Public Class BankingService
<OperationContract()> _
Public Sub ProcessTransaction(ByVal bt As BankingTransaction)
' Code not shown.
End Sub
End Class
' BankingTransaction is not a data contract class,
' but is an XmlSerializer-compatible class instead.
Public Class BankingTransaction
<XmlAttribute()> _
Public Operation As String
<XmlElement()> _
Public fromAccount As Account
<XmlElement()> _
Public toAccount As Account
<XmlElement()> _
Public amount As Integer
End Class
'Notice that the Account class must also be XmlSerializer-compatible.
セキュリティに関する考慮事項
注
シリアル化エンジンを切り替えるときは注意が必要です。 使用されているシリアライザーに応じて、同じ型を XML に異なる方法でシリアル化できます。 誤って間違ったシリアライザーを使用した場合は、開示する予定のない型から情報を開示している可能性があります。
たとえば、 DataContractSerializer クラスは、データ コントラクト型をシリアル化するときに、 DataMemberAttribute 属性でマークされたメンバーのみをシリアル化します。 XmlSerializer クラスは、任意のパブリック メンバーをシリアル化します。 次のコードの型を参照してください。
[DataContract]
public class Customer
{
[DataMember]
public string firstName;
[DataMember]
public string lastName;
public string creditCardNumber;
}
<DataContract()> _
Public Class Customer
<DataMember()> _
Public firstName As String
<DataMember()> _
Public lastName As String
Public creditCardNumber As String
End Class
XmlSerializer クラスが選択されているサービス コントラクトで型が誤って使用された場合、creditCardNumber
メンバーがシリアル化されます。これは、おそらく意図したものではありません。
DataContractSerializer クラスが既定値であっても、DataContractFormatAttribute属性をサービス コントラクトの種類に適用することで、サービスに対して明示的に選択できます (ただし、これは必須ではありません)。
サービスに使用されるシリアライザーはコントラクトの不可欠な部分であり、別のバインドを選択したり、他の構成設定を変更したりして変更することはできません。
その他の重要なセキュリティに関する考慮事項は、 XmlSerializer クラスに適用されます。 まず、 XmlSerializer クラスを使用する WCF アプリケーションには、開示から保護されたキーを使用して署名することを強くお勧めします。 この推奨事項は、 XmlSerializer に手動で切り替える場合と自動スイッチを実行する場合 (Svcutil.exe、サービス参照の追加、または同様のツール) の両方に適用されます。 これは、 XmlSerializer シリアル化エンジンは、アプリケーションと同じキーで署名されている限り、 事前に生成されたシリアル化アセンブリ の読み込みをサポートしているためです。 署名されていないアプリケーションは、事前に生成されたシリアル化アセンブリの予期される名前と一致する悪意のあるアセンブリがアプリケーション フォルダーまたはグローバル アセンブリ キャッシュに配置される可能性から完全に保護されていません。 もちろん、攻撃者はまず、この 2 つの場所のいずれかに書き込みアクセス権を取得して、このアクションを試みる必要があります。
XmlSerializerを使用するたびに存在するもう 1 つの脅威は、システムの一時フォルダーへの書き込みアクセスに関連しています。 XmlSerializerシリアル化エンジンは、このフォルダー内の一時的なシリアル化アセンブリを作成して使用します。 一時フォルダーへの書き込みアクセス権を持つプロセスは、これらのシリアル化アセンブリを悪意のあるコードで上書きする可能性があることに注意してください。
XmlSerializer サポートの規則
コントラクト操作パラメーターまたは戻り値に XmlSerializer互換性のある属性を直接適用することはできません。 ただし、次のコードに示すように、型指定されたメッセージ (メッセージ コントラクト本文部分) に適用できます。
[ServiceContract]
[XmlSerializerFormat]
public class BankingService
{
[OperationContract]
public void ProcessTransaction(BankingTransaction bt)
{
//Code not shown.
}
}
[MessageContract]
public class BankingTransaction
{
[MessageHeader]
public string Operation;
[XmlElement, MessageBodyMember]
public Account fromAccount;
[XmlElement, MessageBodyMember]
public Account toAccount;
[XmlAttribute, MessageBodyMember]
public int amount;
}
<ServiceContract(), XmlSerializerFormat()> _
Public Class BankingService
<OperationContract()> _
Public Sub ProcessTransaction(ByVal bt As BankingTransaction)
'Code not shown.
End Sub
End Class
<MessageContract()> _
Public Class BankingTransaction
<MessageHeader()> _
Public Operation As String
<XmlElement(), MessageBodyMember()> _
Public fromAccount As Account
<XmlElement(), MessageBodyMember()> _
Public toAccount As Account
<XmlAttribute(), MessageBodyMember()> _
Public amount As Integer
End Class
型指定されたメッセージ メンバーに適用すると、これらの属性は、型指定されたメッセージ属性で競合するプロパティをオーバーライドします。 たとえば、次のコードでは、 ElementName
は Name
をオーバーライドします。
[MessageContract]
public class BankingTransaction
{
[MessageHeader] public string Operation;
//This element will be <fromAcct> and not <from>:
[XmlElement(ElementName="fromAcct"), MessageBodyMember(Name="from")]
public Account fromAccount;
[XmlElement, MessageBodyMember]
public Account toAccount;
[XmlAttribute, MessageBodyMember]
public int amount;
}
<MessageContract()> _
Public Class BankingTransaction
<MessageHeader()> _
Public Operation As String
'This element will be <fromAcct> and not <from>:
<XmlElement(ElementName:="fromAcct"), _
MessageBodyMember(Name:="from")> _
Public fromAccount As Account
<XmlElement(), MessageBodyMember()> _
Public toAccount As Account
<XmlAttribute(), MessageBodyMember()> _
Public amount As Integer
End Class
XmlSerializerを使用する場合、MessageHeaderArrayAttribute属性はサポートされません。
注
この場合、 XmlSerializer は、WCF より前にリリースされた例外をスローします。"スキーマの最上位レベルで宣言された要素は、 maxOccurs
> 1 を持つことはできません。
XmlElementAttribute
の代わりにXmlArray
またはXmlArrayItem
を使用するか、ラップされたパラメーター スタイルを使用して、'more' のラッパー要素を指定します。
このような例外が発生した場合は、この状況が適用されるかどうかを調査します。
WCF では、メッセージ コントラクトと操作コントラクトの SoapIncludeAttribute 属性と XmlIncludeAttribute 属性はサポートされていません。代わりに KnownTypeAttribute 属性を使用してください。
IXmlSerializable インターフェイスを実装する型
IXmlSerializable
インターフェイスを実装する型は、DataContractSerializer
で完全にサポートされます。 スキーマを制御するには、 XmlSchemaProviderAttribute 属性を常にこれらの型に適用する必要があります。
Warnung
ポリモーフィック型をシリアル化する場合は、正しい型がシリアル化されるように、 XmlSchemaProviderAttribute を型に適用する必要があります。
IXmlSerializable
を実装する型には、任意のコンテンツを表す型、1 つの要素を表す型、レガシ DataSet型の 3 種類があります。
コンテンツ タイプは、
XmlSchemaProviderAttribute
属性で指定されたスキーマ プロバイダー メソッドを使用します。 このメソッドはnull
を返しません。属性の IsAny プロパティは既定値のfalse
のままにします。 これは、IXmlSerializable
型の最も一般的な使用方法です。要素型は、
IXmlSerializable
型が独自のルート要素名を制御する必要がある場合に使用されます。 型を要素型としてマークするには、XmlSchemaProviderAttribute属性のIsAny プロパティをtrue
に設定するか、スキーマ プロバイダー メソッドからnull
を返します。 スキーマ プロバイダー メソッドを持つことは、要素型では省略可能です。XmlSchemaProviderAttribute
でメソッド名の代わりにnull
を指定できます。 ただし、IsAny
がtrue
され、スキーマ プロバイダー メソッドが指定されている場合、メソッドはnull
を返す必要があります。従来のDataSet型は、
XmlSchemaProviderAttribute
属性でマークされていないIXmlSerializable
型です。 代わりに、スキーマ生成に GetSchema メソッドを使用します。 このパターンはDataSet
型に使用され、型指定されたデータセットは以前のバージョンの .NET Framework でクラスを派生させますが、現在は廃止され、従来の理由でのみサポートされています。 このパターンに依存せず、常にXmlSchemaProviderAttribute
をIXmlSerializable
型に適用してください。
IXmlSerializable コンテンツ タイプ
IXmlSerializable
を実装し、前に定義したコンテンツ タイプである型のデータ メンバーをシリアル化する場合、シリアライザーはデータ メンバーのラッパー要素を書き込み、WriteXml メソッドに制御を渡します。
WriteXml実装では、ラッパー要素への属性の追加を含む任意の XML を書き込むことができます。
WriteXml
が完了すると、シリアライザーは要素を閉じます。
IXmlSerializable
を実装し、前に定義したコンテンツ タイプである型のデータ メンバーを逆シリアル化する場合、デシリアライザーはデータ メンバーのラッパー要素に XML リーダーを配置し、ReadXml メソッドに制御を渡します。 メソッドは、開始タグと終了タグを含む要素全体を読み取る必要があります。
ReadXml
コードが要素が空の場合を処理していることを確認します。 さらに、 ReadXml
実装は、特定の方法で名前が付けられているラッパー要素に依存しないようにする必要があります。 シリアライザーによって選択される名前は異なる場合があります。
たとえば、Object 型のデータ メンバーにIXmlSerializable
コンテンツ タイプをポリモーフィックに割り当てることができます。 また、型インスタンスが null になることも許可されます。 最後に、オブジェクト グラフの保持を有効にし、NetDataContractSerializerを使用して、IXmlSerializable
型を使用できます。 これらすべての機能では、WCF シリアライザーが特定の属性をラッパー要素にアタッチする必要があります (XML スキーマ インスタンス名前空間では "nil" と "type"、WCF 固有の名前空間では "Id"、"Ref"、"Type"、"Assembly")。
ReadXml を実装するときに無視する属性
ReadXml
コードに制御を渡す前に、デシリアライザーは XML 要素を調べ、これらの特殊な XML 属性を検出し、それらに対して動作します。 たとえば、"nil" が true
場合、null 値は逆シリアル化され、 ReadXml
は呼び出されません。 ポリモーフィズムが検出された場合、要素の内容は別の型であるかのように逆シリアル化されます。 ポリモーフィックに割り当てられた型の ReadXml
の実装が呼び出されます。 いずれの場合も、 ReadXml
実装では、これらの特殊な属性はデシリアライザーによって処理されるため、無視する必要があります。
IXmlSerializable コンテンツ タイプのスキーマに関する考慮事項
スキーマと IXmlSerializable
コンテンツ タイプをエクスポートするときに、スキーマ プロバイダー メソッドが呼び出されます。
XmlSchemaSetはスキーマ プロバイダー メソッドに渡されます。 このメソッドは、任意の有効なスキーマをスキーマ セットに追加できます。 スキーマ セットには、スキーマのエクスポートが行われる時点で既に認識されているスキーマが含まれています。 スキーマ プロバイダー メソッドは、スキーマ セットに項目を追加する必要がある場合、適切な名前空間を持つ XmlSchema が既にセットに存在するかどうかを判断する必要があります。 その場合、スキーマ プロバイダー メソッドは、既存の XmlSchema
に新しい項目を追加する必要があります。 それ以外の場合は、新しい XmlSchema
インスタンスを作成する必要があります。 これは、 IXmlSerializable
型の配列が使用されている場合に重要です。 たとえば、名前空間 "B" で型 "A" としてエクスポートされる IXmlSerializable
型がある場合、スキーマ プロバイダー メソッドが呼び出される時点で、スキーマ セットに "ArrayOfA" 型を保持する "B" のスキーマが既に含まれている可能性があります。
コンテンツ タイプのスキーマ プロバイダー メソッドは、 XmlSchemaSetに型を追加するだけでなく、null 以外の値を返す必要があります。 指定したIXmlSerializable
型に使用するスキーマ型の名前を指定するXmlQualifiedNameを返すことができます。 この修飾名は、型のデータ コントラクト名と名前空間としても機能します。 スキーマ プロバイダー メソッドから制御が戻ったときに、スキーマ セットに存在しない型をすぐに返すようにできます。 ただし、関連するすべての型がエクスポートされる (XsdDataContractExporterのすべての関連する型に対して Export メソッドが呼び出され、Schemas プロパティにアクセスされる) までに、その型がスキーマ セットに存在することが予想されます。 関連するすべてのExport
呼び出しが行われる前に Schemas
プロパティにアクセスすると、XmlSchemaExceptionが発生する可能性があります。 エクスポート プロセスの詳細については、「 クラスからのスキーマのエクスポート」を参照してください。
スキーマ プロバイダー メソッドは、使用する XmlSchemaType を返すこともできます。 型は匿名である場合とそうでない場合があります。 匿名の場合、IXmlSerializable
型がデータ メンバーとして使用されるたびに、IXmlSerializable
型のスキーマが匿名型としてエクスポートされます。
IXmlSerializable
型には、引き続きデータ コントラクト名と名前空間があります。 (これは、「 データ コントラクト名 」の説明に従って決定されます。ただし、 DataContractAttribute 属性を使用して名前をカスタマイズすることはできません)。匿名でない場合は、 XmlSchemaSet
のいずれかの型である必要があります。 このケースは、型の XmlQualifiedName
を返すのと同じです。
さらに、型のグローバル要素宣言がエクスポートされます。 型に XmlRootAttribute 属性が適用されていない場合、要素の名前と名前空間はデータ コントラクトと同じになり、"nillable" プロパティは true
。 これに対する唯一の例外は、スキーマ名前空間 (http://www.w3.org/2001/XMLSchema
) です。型のデータ コントラクトがこの名前空間にある場合、対応するグローバル要素は空白の名前空間にあります。これは、スキーマ名前空間に新しい要素を追加することが禁止されているためです。 型に XmlRootAttribute
属性が適用されている場合、グローバル要素宣言は、 ElementName、 Namespace 、および IsNullable プロパティを使用してエクスポートされます。
XmlRootAttribute
が適用される既定値は、データ コントラクト名、空白の名前空間、および "nillable" がtrue
されています。
同じグローバル要素宣言規則が、レガシ データセット型に適用されます。
XmlRootAttribute
は、カスタム コードを介して追加されたグローバル要素宣言をオーバーライドできないことに注意してください。スキーマ プロバイダー メソッドを使用してXmlSchemaSet
に追加するか、レガシ データセット型のGetSchema
を使用します。
IXmlSerializable 要素型
IXmlSerializable
要素型には、 IsAny
プロパティが true
に設定されているか、スキーマ プロバイダー メソッドから null
が返されます。
要素型のシリアル化と逆シリアル化は、コンテンツ タイプのシリアル化と逆シリアル化とよく似ています。 ただし、いくつかの重要な違いがあります。
WriteXml
実装では、1 つの要素 (もちろん複数の子要素を含む可能性があります) を記述することが想定されています。 この 1 つの要素、複数の兄弟要素、または混合コンテンツの外部に属性を書き込むべきではありません。 要素が空の場合があります。ReadXml
実装では、ラッパー要素を読み取らてはなりません。WriteXml
生成される 1 つの要素を読み取ることが期待されます。要素型を定期的にシリアル化する場合 (データ コントラクトのデータ メンバーなど)、シリアライザーは、コンテンツ タイプと同様に、
WriteXml
を呼び出す前にラッパー要素を出力します。 ただし、最上位レベルで要素型をシリアル化する場合、DataContractSerializer
またはNetDataContractSerializer
コンストラクターでシリアライザーを構築するときにルート名と名前空間が明示的に指定されていない限り、シリアライザーは通常、書き込みWriteXml
要素の周囲にラッパー要素を出力しません。 詳細については、「 シリアル化と逆シリアル化」を参照してください。構築時にルート名と名前空間を指定せずに最上位レベルで要素型をシリアル化する場合、 WriteStartObject と WriteEndObject は基本的に何も行わず、 WriteObjectContent 呼び出し
WriteXml
。 このモードでは、シリアル化されるオブジェクトをnull
できず、ポリモーフィックに割り当てることはできません。 また、オブジェクト グラフの保持を有効にできず、NetDataContractSerializer
を使用できません。構築時にルート名と名前空間を指定せずに最上位レベルで要素型を逆シリアル化すると、 IsStartObject は要素の先頭が見つかると
true
を返します。 ReadObjectverifyObjectName
パラメーターをtrue
に設定すると、実際にオブジェクトを読み取る前のIsStartObject
と同じように動作します。ReadObject
次に、ReadXml
メソッドに制御を渡します。
要素の種類に対してエクスポートされるスキーマは、前のセクションで説明した XmlElement
型の場合と同じですが、スキーマ プロバイダー メソッドはコンテンツ タイプと同様に、 XmlSchemaSet にスキーマを追加できます。 要素型で XmlRootAttribute
属性を使用することは許可されず、これらの型に対してグローバル要素宣言は生成されません。
XmlSerializer との違い
IXmlSerializable
インターフェイスとXmlSchemaProviderAttribute
属性とXmlRootAttribute
属性も、XmlSerializerによって認識されます。 ただし、データ コントラクト モデルでのこれらの処理方法にはいくつかの違いがあります。 重要な違いを次の一覧にまとめます。
スキーマ プロバイダー メソッドは、
XmlSerializer
で使用するには public である必要がありますが、データ コントラクト モデルで使用するためにパブリックである必要はありません。スキーマ プロバイダー メソッドは、データ コントラクト モデルで
IsAny
がtrue
されているが、XmlSerializer
では呼び出されない場合に呼び出されます。コンテンツまたはレガシ データセット型に対して
XmlRootAttribute
属性が存在しない場合、XmlSerializer
は空白の名前空間にグローバル要素宣言をエクスポートします。 データ コントラクト モデルでは、使用される名前空間は、通常、前に説明したようにデータ コントラクト名前空間です。
両方のシリアル化テクノロジで使用される型を作成する場合は、これらの違いに注意してください。
IXmlSerializable スキーマのインポート
IXmlSerializable
型から生成されたスキーマをインポートする場合、いくつかの可能性があります。
生成されたスキーマは、「データ コントラクト スキーマ リファレンス」の説明に従って、有効な データ コントラクト スキーマである可能性があります。 この場合、スキーマは通常どおりインポートでき、通常のデータ コントラクト型が生成されます。
生成されたスキーマは、有効なデータ コントラクト スキーマではない可能性があります。 たとえば、スキーマ プロバイダー メソッドでは、データ コントラクト モデルでサポートされていない XML 属性を含むスキーマを生成できます。 この場合、スキーマを
IXmlSerializable
型としてインポートできます。 このインポート モードは既定ではオンになっていませんが、簡単に有効にすることができます。たとえば、/importXmlTypes
コマンド ライン スイッチを ServiceModel メタデータ ユーティリティ ツール (Svcutil.exe) に切り替えます。 詳細については、「 スキーマをインポートしてクラスを生成する」を参照してください。 型インスタンスの XML を直接操作する必要があることに注意してください。 また、より広範なスキーマをサポートする別のシリアル化テクノロジの使用を検討することもできます。XmlSerializer
の使用に関するトピックを参照してください。新しい型を生成する代わりに、プロキシで既存の
IXmlSerializable
型を再利用できます。 この場合、「型を生成するスキーマのインポート」トピックで説明されている参照型機能を使用して、再利用する型を示すことができます。 これは、再利用する型を含むアセンブリを指定する、svcutil.exeでの/reference
スイッチの使用に対応します。
XmlSerializer の従来の動作
.NET Framework 4.0 以前では、XmlSerializer は C# コードをファイルに書き込むことで一時的なシリアル化アセンブリを生成しました。 その後、ファイルはアセンブリにコンパイルされました。 この動作により、シリアライザーの起動時間が遅くなるなど、望ましくない結果が生じることがありました。 .NET Framework 4.5 では、コンパイラを使用せずにアセンブリを生成するようにこの動作が変更されました。 一部の開発者は、生成された C# コードを確認したい場合があります。 この従来の動作を使用するように指定するには、次の構成を使用します。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.xml.serialization>
<xmlSerializer tempFilesLocation='e:\temp\XmlSerializerBug' useLegacySerializerGeneration="true" />
</system.xml.serialization>
<system.diagnostics>
<switches>
<add name="XmlSerialization.Compilation" value="1" />
</switches>
</system.diagnostics>
</configuration>
XmlSerializer
がパブリックでない新しいオーバーライドを使用して派生クラスをシリアル化できないなどの互換性の問題が発生した場合は、次の構成を使用して、XMLSerializer
レガシ動作に戻すことができます。
<configuration>
<appSettings>
<add key="System:Xml:Serialization:UseLegacySerializerGeneration" value="true" />
</appSettings>
</configuration>
上記の構成の代わりに、.NET Framework 4.5 以降のバージョンを実行しているコンピューターで次の構成を使用できます。
<configuration>
<system.xml.serialization>
<xmlSerializer useLegacySerializerGeneration="true"/>
</system.xml.serialization>
</configuration>
注
<xmlSerializer useLegacySerializerGeneration="true"/>
スイッチは、.NET Framework 4.5 以降のバージョンを実行しているコンピューターでのみ機能します。 上記の appSettings
アプローチは、すべての .NET Framework バージョンで機能します。