72

常に列挙型になるジェネリック パラメーター TEnum が与えられた場合、ボックス化/ボックス化解除せずに TEnum から int にキャストする方法はありますか?

このコード例を参照してください。これにより、値が不必要にボックス化/ボックス化解除されます。

private int Foo<TEnum>(TEnum value)
    where TEnum : struct  // C# does not allow enum constraint
{
    return (int) (ValueType) value;
}

上記の C# は、次の IL にコンパイルされたリリース モードです (ボックス化およびボックス化解除のオペコードに注意してください)。

.method public hidebysig instance int32  Foo<valuetype 
    .ctor ([mscorlib]System.ValueType) TEnum>(!!TEnum 'value') cil managed
{
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  box        !!TEnum
  IL_0006:  unbox.any  [mscorlib]System.Int32
  IL_000b:  ret
}

列挙型変換は SO で広く扱われていますが、この特定のケースに対処する議論は見つかりませんでした。

4

9 に答える 9

37

私はパーティーにかなり遅れていることを知っていますが、このような安全なキャストを行う必要がある場合は、 を使用して次のように使用できますDelegate.CreateDelegate

public static int Identity(int x){return x;}
// later on..
Func<int,int> identity = Identity;
Delegate.CreateDelegate(typeof(Func<int,TEnum>),identity.Method) as Func<int,TEnum>

書き込みReflection.Emitや式ツリーがなくても、ボックス化またはボックス化解除せずに int を enum に変換するメソッドがあります。TEnumhere には基になる型が必要であることに注意してくださいint。そうしないと、バインドできないという例外がスローされます。

編集:あまりにも機能し、書くのが少し少ないかもしれない別の方法...

Func<TEnum,int> converter = EqualityComparer<TEnum>.Default.GetHashCode;

これは、32 ビット以下の列挙型を TEnum から int に変換するために機能します。その逆ではありません。.Net 3.5+ では、EnumEqualityComparer基本的にこれを return に変換するように最適化されてい(int)valueます。

デリゲートを使用するとオーバーヘッドが発生しますが、ボクシングよりは確実に優れています。

これはかなり古いものでしたが、まだここに戻ってきて、.net 5/.Net コア (または unsafe パッケージを使用した netfx) で動作し、最適なソリューションを探している場合は...

[JitGeneric(typeof(StringComparison), typeof(int))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryConvert<TEnum, T>(this TEnum @enum, out T val)
    where TEnum : struct, Enum
    where T : struct, IConvertible, IFormattable, IComparable
{
    if (Unsafe.SizeOf<T>() == Unsafe.SizeOf<TEnum>())
    {
        val = Unsafe.As<TEnum, T>(ref @enum);
        return true;
    }
    val = default;
    return false;
}

使用例は次のようになります::

public static int M(MethodImplOptions flags) => flags.TryConvert(out int v) ? v : 0;

ここで、sharplab で、このメソッドが完全にインライン化されていることがわかります:: https://sharplab.io/#gist:802b8d21ee1de26e791294ba48f69d97

于 2010-10-26T18:10:33.090 に答える
19

Reflection.Emit を使用せずに C# でこれが可能かどうかはわかりません。Reflection.Emit を使用すると、列挙型の値をスタックにロードし、それを int として扱うことができます。

ただし、かなり多くのコードを書かなければならないので、これを行うことで本当にパフォーマンスが向上するかどうかを確認する必要があります。

同等の IL は次のようになると思います。

.method public hidebysig instance int32  Foo<valuetype 
    .ctor ([mscorlib]System.ValueType) TEnum>(!!TEnum 'value') cil managed
{
  .maxstack  8
  IL_0000:  ldarg.1
  IL_000b:  ret
}

long列挙型が(64 ビット整数)から派生した場合、これは失敗することに注意してください。

編集

このアプローチに関する別の考え。Reflection.Emit は上記のメソッドを作成できますが、それにバインドする唯一の方法は、仮想呼び出し (つまり、コンパイル時に呼び出すことができる既知のインターフェイス/抽象を実装する) または間接呼び出し (つまりデリゲート呼び出しを介して)。とにかく、これらのシナリオは両方とも、ボックス化/ボックス化解除のオーバーヘッドよりも遅くなると思います。

また、JIT は愚かではなく、これを処理してくれる可能性があることを忘れないでください。(編集 は、元の質問に対する Eric Lippert のコメントを参照してください。現在、ジッターはこの最適化を実行していないと彼は言います。 )

すべてのパフォーマンス関連の問題と同様に、測定、測定、測定!

于 2009-07-27T16:36:56.310 に答える
4

...私はさらに'後で':)

しかし、すべての興味深い仕事をした前の投稿(Michael B)を拡張するだけです

ジェネリックケースのラッパーを作成することに興味を持った(実際にジェネリックを列挙型にキャストしたい場合)

...そして少し最適化...(注:主なポイントは、代わりにFunc <> /delegatesで'as'を使用することです-列挙型として、値型はそれを許可しません)

public static class Identity<TEnum, T>
{
    public static readonly Func<T, TEnum> Cast = (Func<TEnum, TEnum>)((x) => x) as Func<T, TEnum>;
}

...そしてあなたはそれをこのように使うことができます...

enum FamilyRelation { None, Father, Mother, Brother, Sister, };
class FamilyMember
{
    public FamilyRelation Relation { get; set; }
    public FamilyMember(FamilyRelation relation)
    {
        this.Relation = relation;
    }
}
class Program
{
    static void Main(string[] args)
    {
        FamilyMember member = Create<FamilyMember, FamilyRelation>(FamilyRelation.Sister);
    }
    static T Create<T, P>(P value)
    {
        if (typeof(T).Equals(typeof(FamilyMember)) && typeof(P).Equals(typeof(FamilyRelation)))
        {
            FamilyRelation rel = Identity<FamilyRelation, P>.Cast(value);
            return (T)(object)new FamilyMember(rel);
        }
        throw new NotImplementedException();
    }
}

... for(int)-ちょうど(int)rel

于 2012-03-02T19:35:21.123 に答える
3

いつでも System.Reflection.Emit を使用して動的メソッドを作成し、ボックス化せずにこれを行う命令を発行できると思いますが、検証できない場合があります。

于 2009-07-27T16:33:17.520 に答える
3

C# 7.3 のアンマネージ ジェネリック型制約を使用した非常に単純なソリューションを次に示します。

    using System;
    public static class EnumExtensions<TEnum> where TEnum : unmanaged, Enum
    {
        /// <summary>
        /// Converts a <typeparam name="TEnum"></typeparam> into a <typeparam name="TResult"></typeparam>
        /// through pointer cast.
        /// Does not throw if the sizes don't match, clips to smallest data-type instead.
        /// So if <typeparam name="TResult"></typeparam> is smaller than <typeparam name="TEnum"></typeparam>
        /// bits that cannot be captured within <typeparam name="TResult"></typeparam>'s size will be clipped.
        /// </summary>
        public static TResult To<TResult>( TEnum value ) where TResult : unmanaged
        {
            unsafe
            {
                if( sizeof(TResult) > sizeof(TEnum) )
                {
                    // We might be spilling in the stack by taking more bytes than value provides,
                    // alloc the largest data-type and 'cast' that instead.
                    TResult o = default;
                    *((TEnum*) & o) = value;
                    return o;
                }
                else
                {
                    return * (TResult*) & value;
                }
            }
        }

        /// <summary>
        /// Converts a <typeparam name="TSource"></typeparam> into a <typeparam name="TEnum"></typeparam>
        /// through pointer cast.
        /// Does not throw if the sizes don't match, clips to smallest data-type instead.
        /// So if <typeparam name="TEnum"></typeparam> is smaller than <typeparam name="TSource"></typeparam>
        /// bits that cannot be captured within <typeparam name="TEnum"></typeparam>'s size will be clipped.
        /// </summary>
        public static TEnum From<TSource>( TSource value ) where TSource : unmanaged
        {
            unsafe
            {

                if( sizeof(TEnum) > sizeof(TSource) )
                {
                    // We might be spilling in the stack by taking more bytes than value provides,
                    // alloc the largest data-type and 'cast' that instead.
                    TEnum o = default;
                    *((TSource*) & o) = value;
                    return o;
                }
                else
                {
                    return * (TEnum*) & value;
                }
            }
        }
    }

プロジェクト構成で安全でないトグルが必要です。

使用法:

int intValue = EnumExtensions<YourEnumType>.To<int>( yourEnumValue );

編集:Buffer.MemoryCopyダホールの提案からキャストされた単純なポインターに置き換えられました。

于 2018-10-21T10:58:00.400 に答える