13

「CLR経由のC#」を読んでいて、380ページに次のようなメモがあります。

注 Enum クラスは、次のように定義された HasFlag メソッドを定義します。

public Boolean HasFlag(Enum flag);

このメソッドを使用すると、次のように Console.WriteLine への呼び出しを書き直すことができます。

Console.WriteLine("Is {0} hidden? {1}", file, attributes.HasFlag(FileAttributes.Hidden));

ただし、次の理由から、HasFlag メソッドを避けることをお勧めします。

Enum 型のパラメータを取るため、渡す値はすべてボックス化する必要があり、メモリ割り当てが必要です。」

この太字の文が理解できません -- なぜ"

渡す値はすべてボックス化する必要があります

flagパラメーターの型は ですEnum。これは値の型ですが、なぜボクシングがあるのでしょうか? 「渡す値はすべてボックス化する必要があります」とは、 value type を parameter に渡すときにボックス化が発生することを意味するはずEnum flagですよね?

4

8 に答える 8

9

HasFlag<T>(T thing, T flags)拡張メソッドよりも約 30 倍高速なジェネリックはEnum.HasFlag、約 30 行のコードで記述できることに注意してください。拡張メソッドにすることもできます。残念ながら、C# では、そのようなメソッドが列挙型のものだけを受け取るように制限することはできません。その結果、Intellisense は、適用できない型に対してもメソッドをポップアップ表示します。C# や vb.net 以外の言語を使用して拡張メソッドを記述した場合、必要な場合にのみポップアップさせることができると思いますが、そのようなことを試すほど他の言語に精通していません。

internal static class EnumHelper<T1>
{
    public static Func<T1, T1, bool> TestOverlapProc = initProc;
    public static bool Overlaps(SByte p1, SByte p2) { return (p1 & p2) != 0; }
    public static bool Overlaps(Byte p1, Byte p2) { return (p1 & p2) != 0; }
    public static bool Overlaps(Int16 p1, Int16 p2) { return (p1 & p2) != 0; }
    public static bool Overlaps(UInt16 p1, UInt16 p2) { return (p1 & p2) != 0; }
    public static bool Overlaps(Int32 p1, Int32 p2) { return (p1 & p2) != 0; }
    public static bool Overlaps(UInt32 p1, UInt32 p2) { return (p1 & p2) != 0; }
    public static bool Overlaps(Int64 p1, Int64 p2) { return (p1 & p2) != 0; }
    public static bool Overlaps(UInt64 p1, UInt64 p2) { return (p1 & p2) != 0; }
    public static bool initProc(T1 p1, T1 p2)
    {
        Type typ1 = typeof(T1);
        if (typ1.IsEnum) typ1 = Enum.GetUnderlyingType(typ1);
        Type[] types = { typ1, typ1 };
        var method = typeof(EnumHelper<T1>).GetMethod("Overlaps", types);
        if (method == null) method = typeof(T1).GetMethod("Overlaps", types);
        if (method == null) throw new MissingMethodException("Unknown type of enum");
        TestOverlapProc = (Func<T1, T1, bool>)Delegate.CreateDelegate(typeof(Func<T1, T1, bool>), method);
        return TestOverlapProc(p1, p2);
    }
}
static class EnumHelper
{
    public static bool Overlaps<T>(this T p1, T p2) where T : struct
    {
        return EnumHelper<T>.TestOverlapProc(p1, p2);
    }
}

編集:以前のバージョンは、使用された (または少なくとも使用しようとした) ため、壊れていました。EnumHelper<T1, T1>

于 2012-12-18T17:26:57.773 に答える
7

この場合、HasFlagsメソッドに入る前に2回のボクシングの呼び出しが必要です。1つは、値型のメソッド呼び出しを基本型メソッドに解決するためのもので、もう1つは、値型を参照型パラメーターとして渡すためのものです。あなたがそうするならば、あなたはILで同じことを見ることができますvar type = 1.GetType();、文字通りのint1はGetType()呼び出しの前にボックスで囲まれています。メソッド呼び出しのボックス化は、値型定義自体でメソッドがオーバーライドされていない場合にのみ発生するようです。詳細については、こちらを参照してください。値型のメソッドを呼び出すと、.NETでボックス化されますか?

クラスHasFlags引数を取るので、ボクシングはここで発生します。値型とは何かを参照型を期待するものに渡そうとしています。値を参照として表すために、ボクシングが行われます。Enum

Enum値型とその継承( /を使用)に対するコンパイラのサポートはたくさんあり、ValueType説明しようとすると状況が混乱します。とは値型の継承チェーンに含まれているためEnum、ボクシングは突然適用されないと人々は考えているようです。ValueTypeこれが真実である場合、objectすべてがそれを継承するのと同じことが言えますが、私たちが知っているように、これは誤りです。

これは、値型を参照型として表すとボクシングが発生するという事実を止めるものではありません。

boxそして、これをILで証明できます(コードを探してください)。

class Program
{
    static void Main(string[] args)
    {
        var f = Fruit.Apple;
        var result = f.HasFlag(Fruit.Apple);

        Console.ReadLine();
    }
}

[Flags]
enum Fruit
{
    Apple
}



.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 28 (0x1c)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] valuetype ConsoleApplication1.Fruit f,
        [1] bool result
    )

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: box ConsoleApplication1.Fruit
    IL_0009: ldc.i4.0
    IL_000a: box ConsoleApplication1.Fruit
    IL_000f: call instance bool [mscorlib]System.Enum::HasFlag(class [mscorlib]System.Enum)
    IL_0014: stloc.1
    IL_0015: call string [mscorlib]System.Console::ReadLine()
    IL_001a: pop
    IL_001b: ret
} // end of method Program::Main

値型をとして表す場合にも同じことがわかりValueTypeます。これにより、ボックス化も行われます。

class Program
{
    static void Main(string[] args)
    {
        int i = 1;
        ValueType v = i;

        Console.ReadLine();
    }
}


.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 17 (0x11)
    .maxstack 1
    .entrypoint
    .locals init (
        [0] int32 i,
        [1] class [mscorlib]System.ValueType v
    )

    IL_0000: nop
    IL_0001: ldc.i4.1
    IL_0002: stloc.0
    IL_0003: ldloc.0
    IL_0004: box [mscorlib]System.Int32
    IL_0009: stloc.1
    IL_000a: call string [mscorlib]System.Console::ReadLine()
    IL_000f: pop
    IL_0010: ret
} // end of method Program::Main
于 2012-07-26T08:38:54.783 に答える
3

Enum継承元ValueTypeは...クラスです!したがって、ボクシング。

Enumクラスは、基になる型が何であれ、ボックス化された値として任意の列挙を表すことができることに注意してください。のようなFileAttributes.Hidden値は、実数値型 int として表されます。

編集:ここでタイプと表現を区別しましょう。Anintはメモリ内で 32 ビットとして表されます。その型は から派生しValueTypeます。intobjectまたは派生クラス ( ValueTypeclass, class ) に割り当てるとすぐに、Enumそれをボックス化し、その表現をその 32 ビットと追加のクラス情報を含むクラスに効果的に変更します。

于 2012-07-26T08:31:21.427 に答える
0

console.writeline の場合のように、オブジェクトをパラメーターとして受け取るメソッドの値の型を渡すと、固有のボックス化操作が行われます。Jeffery Richter は、あなたが言及した同じ本でこれについて詳しく説明しています。

この場合、console.writeline の string.format メソッドを使用しており、object[] の params 配列を取ります。したがって、ブール値はオブジェクトにキャストされるため、ボクシング操作が行われます。bool で .ToString() を呼び出すことでこれを回避できます。

于 2012-07-26T08:34:00.247 に答える
0

この呼び出しには、1 つだけでなく 2 つのボクシング操作が含まれます。そして、両方が必要な理由は 1 つだけですEnum.HasFlag()thisflag

ほとんどの場合、値は実際には単なるビットのセットであり、コンパイラはメソッド シグネチャで表される型enumから必要なすべての型情報を取得します。enum

ただし、Enum.HasFlags()最初に行うことの場合は、 and を呼び出しthis.GetType()て、flag.GetType()それらが同一であることを確認します。型のないバージョンが必要な場合は、if ((attribute & flag) != 0)を呼び出すのではなく、 を要求しEnum.HasFlags()ます。

于 2012-07-26T09:14:03.560 に答える
0

さらに、 には複数のボクシング がありEnum.HasFlagます。

public bool HasFlag(Enum flag)
{
    if (!base.GetType().IsEquivalentTo(flag.GetType()))
    {
        throw new ArgumentException(Environment.GetResourceString("Argument_EnumTypeDoesNotMatch", new object[]
        {
            flag.GetType(),
            base.GetType()
        }));
    }
    ulong num = Enum.ToUInt64(flag.GetValue());
    ulong num2 = Enum.ToUInt64(this.GetValue());
    return (num2 & num) == num;
}

GetValueメソッド呼び出しを見てください。

更新します。MS は .NET 4.5 でこのメソッドを最適化したようです (ソース コードは referencesource からダウンロードされています)。

    [System.Security.SecuritySafeCritical]
    public Boolean HasFlag(Enum flag) { 
        if (flag == null)
            throw new ArgumentNullException("flag"); 
        Contract.EndContractBlock(); 

        if (!this.GetType().IsEquivalentTo(flag.GetType())) { 
            throw new ArgumentException(Environment.GetResourceString("Argument_EnumTypeDoesNotMatch", flag.GetType(), this.GetType()));
        }

        return InternalHasFlag(flag); 
    }

    [System.Security.SecurityCritical]  // auto-generated 
    [ResourceExposure(ResourceScope.None)]
    [MethodImplAttribute(MethodImplOptions.InternalCall)] 
    private extern bool InternalHasFlag(Enum flags);
于 2012-07-26T08:46:20.273 に答える