web-dev-qa-db-ja.com

C#の列挙型制約

可能性のある複製:
enumジェネリック制約がないため、誰でも良い回避策を知っていますか?

C#がEnumの型制約を許可しない背後にある理由は何ですか?狂気の背後には方法があると確信していますが、なぜそれが不可能なのかを理解したいと思います。

以下は、(理論的に)私ができることです。

public static T GetEnum<T>(this string description) where T : Enum
{
...
}
149
Taylor Leese

これは、時々リクエストされる機能です。

私が指摘するのが好きなように、誰かが機能を設計、仕様、実装、テスト、文書化し、出荷するまで、すべての機能は実装されていません。これまでのところ、これについては誰もそれを行っていません。特別な異常な理由はありません。他にもやるべきことがたくさんあり、予算が限られていますが、これは「これはいいことではないでしょうか」を超えたことはありません。言語設計チームでの議論。

CLRはそれをサポートしていないため、それを機能させるためには、言語作業に加えてランタイム作業を行う必要があります。 (回答のコメントを参照)

いくつかのまともな使用例があることがわかりますが、どれもそれほど魅力的ではなく、より頻繁に要求されるか、より説得力があり、より広範囲に及ぶ他の数百の機能の1つではなく、この作業を行います使用例。 (このコードをいじるつもりなら、私は個人的にデリゲート制約を優先し、列挙制約よりも優先します。)

92
Eric Lippert

実際には、いトリックでそれは可能です。ただし、拡張メソッドには使用できません。

public abstract class Enums<Temp> where Temp : class {
    public static TEnum Parse<TEnum>(string name) where TEnum : struct, Temp {
        return (TEnum)Enum.Parse(typeof(TEnum), name); 
    }
}
public abstract class Enums : Enums<Enum> { }

Enums.Parse<DateTimeKind>("Local")

必要に応じて、Enums<Temp>プライベートコンストラクタと、TempEnumであるパブリックのネストされた抽象継承クラス。非列挙型の継承バージョンを防止します。

このトリックを使用して拡張メソッドを作成することはできません。

145
SLaks
public static T GetEnum<T>(this string description) where T : struct
{
    return (T)Enum.Parse(typeof(T), description);
}

それはあなたの質問に答えますか?

15
Andrei Sedoi

ExtraConstraints を使用したILウィービング

あなたのコード

public static T GetEnum<[EnumConstraint] T>(this string description)
{
    ...
}

コンパイルされるもの

public static T GetEnum<T>(this string description) where T : Enum
{
    ...
}
7
Simon

これは、VB.NETバージョンの SLaks excellent ugly trick で、Importsを「typedef」として使用しています:(型推論は期待どおりに機能しますが、拡張メソッドを取得することはできません。)

'Base namespace "EnumConstraint"
Imports Enums = EnumConstraint.Enums(Of System.Enum)

Public NotInheritable Class Enums(Of Temp As Class)
Private Sub New()
End Sub

Public Shared Function Parse(Of TEnum As {Temp, Structure})(ByVal Name As String) As TEnum
    Return DirectCast([Enum].Parse(GetType(TEnum), Name), TEnum)
End Function

Public Shared Function IsDefined(Of TEnum As {Temp, Structure})(ByVal Value As TEnum) As Boolean
    Return [Enum].IsDefined(GetType(TEnum), Value)
End Function

Public Shared Function HasFlags(Of TEnum As {Temp, Structure})(ByVal Value As TEnum, ByVal Flags As TEnum) As Boolean
    Dim flags64 As Long = Convert.ToInt64(Flags)
    Return (Convert.ToInt64(Value) And flags64) = flags64
End Function

End Class

Module Module1

Sub Main()

    Dim k = Enums.Parse(Of DateTimeKind)("Local")
    Console.WriteLine("{0} = {1}", k, CInt(k))
    Console.WriteLine("IsDefined({0}) = {1}", k, Enums.IsDefined(k))
    k = DirectCast(k * 2, DateTimeKind)
    Console.WriteLine("IsDefined({0}) = {1}", k, Enums.IsDefined(k))

    Console.WriteLine(" {0} same as {1} Or {2}: {3} ", IO.FileAccess.ReadWrite, IO.FileAccess.Read, IO.FileAccess.Write, _
                      Enums.HasFlags(IO.FileAccess.ReadWrite, IO.FileAccess.Read Or IO.FileAccess.Write))

    ' These fail to compile as expected:
    'Console.WriteLine(Enums.HasFlags(IO.FileAccess.ReadWrite, IO.FileOptions.RandomAccess))
    'Console.WriteLine(Enums.HasFlags(Of IO.FileAccess)(IO.FileAccess.ReadWrite, IO.FileOptions.RandomAccess))

    If Debugger.IsAttached Then _
        Console.ReadLine()
End Sub

End Module

出力:

Local = 2
IsDefined(Local) = True
IsDefined(4) = False
 ReadWrite same as Read Or Write: True
3
Mark Hurd

ここで風変わりなことの1つは、列挙の「ベース」タイプに実装が依存する、かなりの数のジェネリックEnumメソッドを記述する必要があることです。

列挙の「ベース」タイプEとは、名前が_System.TypeCode_列挙のメンバーの名前と同じ名前を持つSystem名前空間のタイプを意味します。タイプEに対して System.Type.GetTypeCode(System.Type) を呼び出します。列挙がC#で宣言された場合、これは「継承」するように宣言されたものと同じ型です(仕様でこれが正式に呼ばれているのかわかりません)。たとえば、以下のAnimal列挙型の基本型は_System.Byte_です。

_public enum Animal : byte
{
    Moose,
    Squirrel
}
_

Switchステートメントを使用してこのようなメソッドを作成することは可能ですが、見苦しいことは確かです。強く型付けされたパラメーターを取得したり、型が列挙の基本型である型を返したりすることはできません。 (たとえば、メソッドを含むジェネリック型の静的コンストラクター内)。

2
Doug McClean