F#は言語としてはとても素晴らしい設計をしていますが、コンパイルされたアセンブリは結構残念だったりします。F# + Entity Framewrok で ASP.NET WebAPI サーバ立てたら、返ってくる JSON がおかしいという記事を見かけたのでスコープについてまとめておこうと思います。
まずF#言語でアクセス制御するためにはpublic internal privateの3つのキーワードが用意されています。次にアセンブリでのアクセス制御についてはCorHdr.hに定義されています。関連部分を引用すると
// TypeDef/ExportedType attr bits, used by DefineTypeDef.
typedef enum CorTypeAttr
{
// Use this mask to retrieve the type visibility information.
tdVisibilityMask = 0x00000007,
tdNotPublic = 0x00000000, // Class is not public scope.
tdPublic = 0x00000001, // Class is public scope.
tdNestedPublic = 0x00000002, // Class is nested with public visibility.
tdNestedPrivate = 0x00000003, // Class is nested with private visibility.
tdNestedFamily = 0x00000004, // Class is nested with family visibility.
tdNestedAssembly = 0x00000005, // Class is nested with assembly visibility.
tdNestedFamANDAssem = 0x00000006, // Class is nested with family and assembly visibility.
tdNestedFamORAssem = 0x00000007, // Class is nested with family or assembly visibility.
...
} CorTypeAttr;
// MethodDef attr bits, Used by DefineMethod.
typedef enum CorMethodAttr
{
// member access mask - Use this mask to retrieve accessibility information.
mdMemberAccessMask = 0x0007,
mdPrivateScope = 0x0000, // Member not referenceable.
mdPrivate = 0x0001, // Accessible only by the parent type.
mdFamANDAssem = 0x0002, // Accessible by sub-types only in this Assembly.
mdAssem = 0x0003, // Accessibly by anyone in the Assembly.
mdFamily = 0x0004, // Accessible only by type and sub-types.
mdFamORAssem = 0x0005, // Accessibly by sub-types anywhere, plus anyone in assembly.
mdPublic = 0x0006, // Accessibly by anyone who has visibility to this scope.
// end member access mask
...
} CorMethodAttr;
という感じです。
先に
C#言語のアクセス制御の説明をしておくとわかりやすいでしょうか。C#言語ではpublic private protected internal protected internalの5種類です。これがアセンブリとどのような対応をしているかというと非常にわかりやすく
C#クラス | アセンブリ表現 |
---|
public | tdPublic |
internal | tdNotPublic |
C#メンバー | アセンブリ表現 |
---|
public | mdPublic |
private | mdPrivate |
protected | mdFamily |
internal | mdAssem |
protected internal | mdFamORAssem |
となります。
さて本題のF#言語ですが、C#より種類が少ないはずなのに予想を超える複雑さをしています。まずクラスですが、
F#クラス | アセンブリ表現 | C#相当クラス |
---|
public | tdPublic | public |
private | tdNotPublic | internal |
internal | tdNotPublic | internal |
と順当です。次にメンバーですが
F#メンバー | アセンブリ表現 | C#相当メンバー |
---|
public | mdPublic | public |
private | mdAssem | internal |
internal | mdAssem | internal |
…はい、F#上ではクラス外からアクセスできなくなるprivateですがC#のinternalに相当しアクセス可能となります。まだ簡単に見えますか? 恐ろしいのはここからです。F#にもC#にも自動実装プロパティがあります。プロパティの値を保持するためにコンパイラが自動的にフィールド(backing field)を用意しプロパティアクセッサを実装する機能です。当然、コンパイラによる自動的なフィールドである以上、プログラムからアクセスできるべきではありません。実際、C#ではmdPrivate、privateフィールドと同等です。さてF#はそうではありません。
F#メンバー | アセンブリ表現 | C#相当メンバー |
---|
public | mdPublic | public |
private | mdAssem | internal |
internal | mdAssem | internal |
backing field | mdAssem | internal |
…はい、privateメンバーと同様にC#のinternal fieldに相当します。まだ簡単に見えますか? 実はこの表はまだ不完全です。F#ではクラス自身のスコープがメンバーのスコープに影響を与えます。
F#クラス | F#メンバー | アセンブリ表現 | C#相当メンバー |
---|
public | public | mdPublic | public |
private | mdAssem | internal |
internal | mdAssem | internal |
backing field | mdAssem | internal |
private | public | mdAssem | internal |
private | mdAssem | internal |
internal | mdAssem | internal |
backing field | mdAssem | internal |
internal | public | mdAssem | internal |
private | mdAssem | internal |
internal | mdAssem | internal |
backing field | mdAssem | internal |
…要するにpublicクラスのpublicメンバーだけがC#のpublic相当であり、それ以外はなんであれ全てC#のinternal相当です。ちなみにmutableでないフィールドにfdInitOnly(C#におけるreadonly)が付けられていないため、アセンブリ内からであれば書き換え可能という問題もあります。
これを踏まえて要望をまとめておきます。
- backing fieldはmdPrivateにして欲しい
- privateメンバーもmdPrivateにして欲しい
- internal / privateクラスであってもpublicメンバーはmdPublicにして欲しい
といったところでしょうか。
0 件のコメント:
コメントを投稿