10

更新:C#の回避策については、この質問の下部を参照してください。

こんにちは、

次の拡張メソッドを考えてみましょう。

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
}

これは、クラスが通常継承を許可されていないため、コンパイル時にエラーをスローしますSystem.Enum。問題は、指定された列挙体がenumキーワードは実際に継承していますSystem.Enum上のコードは、拡張メソッドを列挙型に限定する理想的な方法です。

今ここでの明らかな回避策は、Enumの代わりにTしかし、あなたはジェネリック型の利点を失います:

MyEnum e;
e.HasFlags(MyOtherEnum.DoFunkyStuff);

上記のコードはジェネリック型を使用してコンパイル時エラーをスローしますが、Enum私はそうするために実装している場合)

制約チェックを無効にするために使用できるコンパイラオプションはありますか?これを行うには他にも素晴らしい方法がありますか?

それが示唆される前に、私は使用しないと言っていますwhere T : structまたはいくつかのような、以来、あなたは奇妙なものを行うことができるだろう123.HasFlags(456)

私はなぜこのエラーが全く存在するのか困惑しています...あなたが使っているのと同じ問題ですwhere T : System.Objectしかし、それはあなたが持っているwhere T : class...なぜそうでないwhere T : enum

C#の回避策

Jon Skeetは、クラスをコンパイルしてIEnumConstraintこれを次に、System.Enumポストビルド。これは、現時点でこの問題を回避するために最も近い人が近づくことができると私は信じています。

見る:

この回避策が実現できない場合は、ライブラリをC ++ / CLIコードとして記述する必要があります。これはジェネリック型の制約に使用できるものを制限しません(下記の私の答えのコードを参照してください)。


  • 非常にまれなケースでは、このような制約が必要だとは思わなかったかもしれません。誰が知っている...それが事実だったら私は彼らに同意するだろう:) - Skurmedel
  • @スカーメル:奇妙なことは、特に禁止されているということです。言い換えれば、仕様はより詳細になっています複雑なそれを制限する。理由がわかりません。多分Eric Lippertが説明します:) - Jon Skeet
  • はい、私はJon Skeetに同意しますが、それは見逃されている可能性があります(クラス継承の場合と同じ継承チェックを使用しています)。しかし、C#の内部動作にはあまり慣れていません。 - Blixt
  • Eric LippertはC#でEnum型制約について以前に投稿しました:stackoverflow.com/questions/1331739/enum-type-constraints-in-c/… - Randy Levy
  • そうですか。コンパイラのエラーを無効にするオプションがあったとしても、コードはまだ動作しないという彼の答えから取ることができますか?そのような場合を除き、私はこの制限がなぜ私に強制されているのか分かりません(CS0702のエラーを明示的にオフにすることは良いことです)。 - Blixt

5 답변


10

編集:ildasm / ilasmでこれをサポートするライブラリが利用可能になりました:制約のないメロディ


C#チームのメンバーは、以前は好きなサポートできるようにするwhere T : Enumそしてwhere T : Delegateしかし、それは決して十分高い優先順位ではなかったことを意味します。 (私は推論が最初の場所で制限を受けていることは確かではありませんが、確かに...)

C#の最も実用的な回避策は次のとおりです。

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    // ...
}

コンパイル時に "enum-ness"のチェックが失われますが、両方の場所で同じタイプを使用していることを確認することができます。もちろん、チェックの実行時のペナルティもあります。静的コンストラクタで例外をスローする実装に汎用のネストされた型を使用すると、最初の呼び出し後に実行時のペナルティを回避できます。

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    return EnumHelper<T>.HasFlags(value, flags);
}

private class EnumHelper<T> where T : struct
{
    static EnumHelper()
    {
        if (!typeof(Enum).IsAssignableFrom(typeof(T))
        {
            throw new InvalidOperationException(); // Or something similar
        }
    }

    internal static HasFlags(T value, T flags)
    {
        ...
    }
}

Grecoが言及しているように、C ++ / CLIでメソッドを記述してから、別のオプションとしてC#のクラスライブラリを参照できます。


  • 拡張メソッドの範囲を限定的に保つことが重要だと思いますので、私は使用しませんwhere T : struct私の質問で言及された理由のために(123.HasFlags(456))私はGrecoのソリューションが好きですが、1つのメソッドのライブラリ全体の作成を正当化できません。 「注入する」プロセスをセットアップすることは可能であるか?コンパイル時にC ++ / CLIをC#ライブラリに追加する - Blixt
  • @Blixt:まあ、バイナリの書き換えを見ることはできますが、おそらくは醜いでしょう。デリゲートと列挙型でできる便利な機能のために、オープンソースプロジェクトに興味があるのだろうか? - Jon Skeet
  • 図書館の考えの周りのブログの投稿...msmvps.com/blogs/jon_skeet/archive/2009/09/10/… - Jon Skeet
  • あなたはILMerge(research.microsoft.com/en-us/people/mbarnett/ilmerge.aspx)あなたのメインコードにかなり痕跡のあるC ++ / CLIアセンブリーを含めるようにしました。 - Steve Gilham
  • @ Yngvar:まあ、今はcodeblog.jonskeet.ukです:codeblog.jonskeet.uk/2009/09/10/… - Jon Skeet

7

それには回避策があります

ここをクリック


  • 回避策は機能しますが、「C#3.0では」ありません。 - Lasse Vågsæther Karlsen
  • うーん...拡張メソッドが1つしかないので、新しいC ++プロジェクトを作成することはできません。 C ++ライブラリをC#ライブラリにコンパイルする環境をセットアップすることは可能でしょうか? - Blixt
  • これは素晴らしい回避策です。私はなぜこれが4年以上経っても知られていないのだろうと思っています... - Daniel Brückner
  • 私は回避策を実装し、それは完全に動作します!残念ながら、ユーティリティライブラリ全体をC ++コードにしない限り、私はそれを使用していません...とにかく、コードに関するこの質問に対する私の答えを見てください。 - Blixt

3

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

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>プライベートコンストラクタとパブリックネストされた抽象継承クラスTempとしてEnum継承されていないバージョンのenumを防ぐためです。



2

私はC + +の回避策に行くことに抵抗することができませんでしたし、私はそれを仕事に持って以来、私はあなたの残りの部分と共有するだろうと思った!

ここではC ++のコードです(私のC ++は非常に錆びていますので、特に引数がどのように定義されているか、エラーを指摘してください):

#include "stdafx.h"

using namespace System;
using namespace System::Runtime::CompilerServices;

namespace Blixt
{
namespace Utilities
{
    [Extension]
    public ref class EnumUtility abstract sealed
    {
    public:
        generic <typename T> where T : value class, Enum
        [Extension]
        static bool HasFlags(T value, T flags)
        {
            __int64 mask = Convert::ToInt64(flags);
            return (Convert::ToInt64(value) & mask) == mask;
        }
    };
}
}

テスト用のC#コード(コンソールアプリケーション):

using System;
using Blixt.Utilities;

namespace Blixt.Playground
{
    [Flags]
    public enum Colors : byte
    {
        Black = 0,
        Red = 1,
        Green = 2,
        Blue = 4
    }

    [Flags]
    public enum Tastes : byte
    {
        Nothing = 0,
        Sour = 1,
        Sweet = 2,
        Bitter = 4,
        Salty = 8
    }

    class Program
    {
        static void Main(string[] args)
        {
            Colors c = Colors.Blue | Colors.Red;
            Console.WriteLine("Green and blue? {0}", c.HasFlags(Colors.Green | Colors.Red));
            Console.WriteLine("Blue?           {0}", c.HasFlags(Colors.Blue));
            Console.WriteLine("Green?          {0}", c.HasFlags(Colors.Green));
            Console.WriteLine("Red and blue?   {0}", c.HasFlags(Colors.Red | Colors.Blue));

            // Compilation error:
            //Console.WriteLine("Sour?           {0}", c.HasFlags(Tastes.Sour));

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey(true);
        }
    }
}


1

あなたはIL製織を使用してこれを達成することができます。余分な制約

このコードを書くことができます

public static bool HasFlags<[EnumConstraint] T>(this T value, T flags)
{
    // ...
} 

コンパイルされるもの

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
} 

リンクされた質問


関連する質問

最近の質問