F#の列挙型はC#の列挙型と同じです。その定義は表面上、共用体型の空のケースとまったく同じように見えますが、気をつけるべき違いがたくさんあります。

列挙型の定義

列挙型を定義するには、空のケースを持つ共用体型とまったく同じ構文を使います。ただし、各ケースに定数値を指定する必要があり、その定数はすべて同じ型でなければいけません。

type SizeUnion = Small | Medium | Large         // 共用体
type ColorEnum = Red=0 | Yellow=1 | Blue=2      // 列挙型

文字列は使えず、intやbyteやcharなどの互換性のある型だけが使えます。

type MyEnum = Yes = "Y" | No ="N"  // エラー。文字列は使えません。
type MyEnum = Yes = 'Y' | No ='N'  // charを使っているのでOK。

共用体型では、ケースが大文字で始まる必要がありますが、列挙型ではその必要はありません。

type SizeUnion = Small | Medium | large      // エラー。"large"は無効です。
type ColorEnum = Red=0 | Yellow=1 | blue=2      // OK

C#と同じように、ビットフラグには FlagsAttribute を使えます。

[<System.FlagsAttribute>]
type PermissionFlags = Read = 1 | Write = 2 | Execute = 4 
let permission = PermissionFlags.Read ||| PermissionFlags.Write

列挙型の構築

共用体型と違って、列挙型を構築するときは必ず修飾名を使う必要があります。

let red = Red            // エラー。列挙型は修飾が必要です
let red = ColorEnum.Red  // OK 
let small = Small        // OK。共用体は修飾が不要です

また、基底となるint型とのキャストもできます。

let redInt = int ColorEnum.Red  
let redAgain:ColorEnum = enum redInt // 指定した列挙型にキャスト 
let yellowAgain = enum<ColorEnum>(1) // または直接作成

列挙されたリストにない値を作ることもできます。

let unknownColor = enum<ColorEnum>(99)   // 有効

また、共用体型と違って、C#と同じようにBCL(Base Class Library)のEnum関数を使って値を列挙したりパースしたりできます。

let values = System.Enum.GetValues(typeof<ColorEnum>)
let redFromString =  
    System.Enum.Parse(typeof<ColorEnum>,"Red") 
    :?> ColorEnum  // ダウンキャストが必要

列挙型のマッチング

列挙型にマッチングするときも、必ず修飾名を使う必要があります。

let unqualifiedMatch x = 
    match x with
    | Red -> printfn "赤"             // 警告 FS0049
    | _ -> printfn "その他" 

let qualifiedMatch x = 
    match x with
    | ColorEnum.Red -> printfn "赤"   //OK。修飾名を使っています。
    | _ -> printfn "その他"

共用体と列挙型の両方で、パターンマッチングのときに既知のすべてのケースをカバーしていないと、警告が出ます。

let matchUnionIncomplete x = 
    match x with
    | Small -> printfn "小"   
    | Medium -> printfn "中"   
    // 警告:不完全なパターンマッチ

let matchEnumIncomplete x = 
    match x with
    | ColorEnum.Red -> printfn "赤"   
    | ColorEnum.Yellow -> printfn "黄"   
    // 警告:不完全なパターンマッチ

共用体と列挙型の大きな違いの一つは、すべての共用体型をリストアップすれば、コンパイラに完全なパターンマッチングと認識させられる点です。

列挙型の場合はそうではありません。事前に宣言していない列挙型を作って、それとマッチングを試みると、ランタイム例外が起きる可能性があります。 そのため、既知のすべての列挙型を明示的にリストアップしていても、コンパイラは警告を出します。

// コンパイラはまだ満足していません
let matchEnumIncomplete2 x = 
    match x with
    | ColorEnum.Red -> printfn "赤"   
    | ColorEnum.Yellow -> printfn "黄"   
    | ColorEnum.Blue -> printfn "青"   
    // 値 '3' はパターンでカバーしていないケースを示している可能性があります。

これを解決する唯一の方法は、事前に宣言した範囲外の列挙型を処理するために、ケースの最後にワイルドカードを追加することです。

// コンパイラがようやく満足します
let matchEnumComplete x = 
    match x with
    | ColorEnum.Red -> printfn "赤"   
    | ColorEnum.Yellow -> printfn "黄"   
    | ColorEnum.Blue -> printfn "青"   
    | _ -> printfn "その他"   

// 未知のケースでテスト    
let unknownColor = enum<ColorEnum>(99)   // 有効
matchEnumComplete unknownColor

まとめ

一般的には、 int 値を関連付ける必要がある場合や、他の.NET言語に公開する必要のある型を書いている場合を除き、 列挙型より判別共用体型を使うほうがいいでしょう。

results matching ""

    No results matching ""