Adakah yang tahu solusi yang baik untuk kurangnya kendala umum enum?

90

Yang ingin saya lakukan adalah seperti ini: Saya memiliki enum dengan gabungan nilai yang ditandai.

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo ) 
        where T:enum //the constraint I want that doesn't exist in C#3
    {    
        return (input & matchTo) != 0;
    }
}

Jadi saya bisa melakukan:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

if( tester.IsSet( MyEnum.FlagA ) )
    //act on flag a

Sayangnya, C # generik dimana batasan tidak memiliki batasan enum, hanya class dan struct. C # tidak melihat enum sebagai struct (meskipun mereka adalah tipe nilai) jadi saya tidak bisa menambahkan tipe ekstensi seperti ini.

Apakah ada yang tahu solusinya?

Keith
sumber
2
Keith: unduh versi 0.0.0.2 dari UnconstrainedMelody - Saya telah menerapkan HasAll dan HasAny. Nikmati.
Jon Skeet
Apa yang Anda maksud dengan "C # tidak melihat enum sebagai struct"? Anda dapat menggunakan tipe enum sebagai parameter tipe yang dibatasi dengan structbaik.
Timwi
memeriksa artikel ini di sini: codeproject.com/KB/cs/ExtendEnum.aspx 'IsValidEnumValue' atau metode 'IsFlagsEnumDefined' mungkin jawaban atas pertanyaan Anda.
dmihailescu
1
Beri suara untuk ide uservoice ini , jika Anda ingin melihatnya di dalam .net suatu hari nanti.
Matthieu
11
C # 7.3 memperkenalkan kendala enum.
Marc Sigrist

Jawaban:

49

EDIT: Ini sekarang tayang dalam versi 0.0.0.2 dari UnconstrainedMelody.

(Seperti yang diminta di posting blog saya tentang kendala enum . Saya telah menyertakan fakta dasar di bawah ini demi jawaban yang berdiri sendiri.)

Solusi terbaik adalah menunggu saya memasukkannya ke dalam UnconstrainedMelody 1 . Ini adalah pustaka yang mengambil kode C # dengan batasan "palsu" seperti

where T : struct, IEnumConstraint

dan mengubahnya menjadi

where T : struct, System.Enum

melalui langkah postbuild.

Seharusnya tidak terlalu sulit untuk menulis IsSet... meskipun melayani bendera berbasis Int64dan UInt64berbasis bisa menjadi bagian yang sulit. (Saya mencium beberapa metode pembantu yang akan datang, pada dasarnya memungkinkan saya untuk memperlakukan setiap flag enum seolah-olah itu memiliki tipe dasar UInt64.)

Perilaku apa yang Anda inginkan jika dipanggil

tester.IsSet(MyFlags.A | MyFlags.C)

? Haruskah itu memeriksa bahwa semua bendera yang ditentukan disetel? Itulah harapan saya.

Saya akan mencoba melakukan ini dalam perjalanan pulang malam ini ... Saya berharap mendapatkan kilasan cepat tentang metode enum yang berguna untuk membuat perpustakaan mencapai standar yang dapat digunakan dengan cepat, lalu bersantai sedikit.

EDIT: Omong IsSet-omong, saya tidak yakin tentang nama. Pilihan:

  • Termasuk
  • Mengandung
  • HasFlag (atau HasFlags)
  • IsSet (ini tentu saja merupakan pilihan)

Pikiran diterima. Aku yakin itu akan memakan waktu sebelum apapun menjadi batu ...


1 atau kirimkan sebagai tambalan, tentu saja ...

Jon Skeet
sumber
1
Anda harus pergi dan menyebutkan PostSharp LOL: o postsharp.org/blog/generic-constraints-for-enums-and-delegates
Sam Harwell
1
Atau sebenarnya lebih sederhana HasAny () dan HasAll ()
Keith
1
Ya, saya setuju itu lebih baik. colors.HasAny(Colors.Red | Colors.Blue)Sepertinya kode yang sangat mudah dibaca. =)
Blixt
1
Yup, saya juga suka HasAny dan HasAll. Akan pergi dengan itu.
Jon Skeet
5
Sejak C # 7.3 (dirilis Mei 2018), dimungkinkan untuk menggunakan batasan where T : System.Enum. Ini sudah ditulis di tempat lain di utas; hanya berpikir saya akan mengulanginya di sini.
Jeppe Stig Nielsen
16

Darren, itu akan berfungsi jika jenisnya adalah pencacahan khusus - agar pencacahan umum berfungsi, Anda harus memasukkannya ke int (atau lebih mungkin uint) untuk melakukan matematika boolean:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}
Ronnie
sumber
1
Dan jika Anda memiliki sejumlah bendera yang konyol, Anda dapat memanggil GetTypeCode () pada argumen dan Convert.ToUint64 ()
Kit
Luar biasa, kombinasi 'Enum' dan Convert.ToUInt32saya tidak menemukan tempat lain. AFAIK, Ini satu-satunya solusi Pre-Net-4 yang layak yang juga berfungsi di VB. BTW, jika matchTomungkin memiliki beberapa bit bendera, maka ganti != 0dengan == Convert.ToUInt32(matchTo).
ToolmakerSteve
1
Perhatikan bahwa Convert.ToUInt32digunakan dengan enum akan menggunakan Convert.ToUInt32(object)overload, yang berarti bahwa CLR akan mengemas nilai-nilai ini terlebih dahulu sebelum meneruskannya ke ToUInt32metode. Dalam kebanyakan kasus, ini tidak masalah, tetapi ada baiknya mengetahui bahwa Anda akan membuat GC agak sibuk jika Anda menggunakan sesuatu seperti ini untuk mengurai jutaan enum per detik.
Groo
10

Sebenarnya, itu mungkin, dengan trik yang jelek. Namun, ini tidak dapat digunakan untuk metode ekstensi.

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.IsSet<DateTimeKind>("Local")

Jika mau, Anda bisa memberikan Enums<Temp>konstruktor privat dan kelas warisan abstrak bersarang publik dengan Tempas Enum, untuk mencegah versi turunan untuk non-enum.

SLaks
sumber
8

Anda dapat mencapai ini menggunakan IL Weaving dan ExtraConstraints

Memungkinkan Anda menulis kode ini

public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
}

Apa yang dikompilasi

public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}
Simon
sumber
6

Mulai C # 7.3, Anda dapat menggunakan batasan Enum pada tipe generik:

public static TEnum Parse<TEnum>(string value) where TEnum : Enum
{
    return (TEnum) Enum.Parse(typeof(TEnum), value);
}

Jika Anda ingin menggunakan Nullable enum, Anda harus meninggalkan batasan struct orginial:

public static TEnum? TryParse<TEnum>(string value) where TEnum : struct, Enum
{
    if( Enum.TryParse(value, out TEnum res) )
        return res;
    else
        return null;
}
Mik
sumber
4

Ini tidak menjawab pertanyaan awal, tetapi sekarang ada metode di .NET 4 yang disebut Enum.HasFlag yang melakukan apa yang Anda coba lakukan dalam contoh Anda

Phil Devaney
sumber
Suara positif karena pada titik ini, sebagian besar orang harus menggunakan .NET 4 (atau lebih tinggi) sehingga mereka harus menggunakan metode ini daripada mencoba meretasnya bersama-sama.
CptRobby
Suara positif. Namun solusi mereka menggunakan tinju argumen flag. .NET 4.0 adalah lima tahun sekarang.
Jeppe Stig Nielsen
3

Cara saya melakukannya adalah dengan meletakkan batasan struct, kemudian memeriksa bahwa T adalah enum saat runtime. Ini tidak menghilangkan masalah sepenuhnya, tetapi sedikit menguranginya

kandang
sumber
7
di mana T: struct, IComparable, IFormattable, IConvertible - ini adalah yang terdekat yang bisa Anda dapatkan dengan enum :)
Kit
1

Menggunakan kode asli Anda, di dalam metode ini Anda juga dapat menggunakan refleksi untuk menguji bahwa T adalah enum:

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo )
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("Must be an enum", "input");
        }
        return (input & matchTo) != 0;
    }
}
Scott Dorman
sumber
2
Terima kasih, tetapi hal itu mengubah masalah waktu kompilasi (batasan tempat) menjadi masalah waktu proses (pengecualian Anda). Anda juga masih perlu mengubah input menjadi int sebelum Anda dapat melakukan apa pun dengannya.
Keith
1

Berikut beberapa kode yang baru saja saya lakukan yang tampaknya berfungsi seperti yang Anda inginkan tanpa harus melakukan sesuatu yang terlalu gila. Ini tidak terbatas hanya pada enum yang disetel sebagai Flags, tetapi selalu ada pemeriksaan yang dimasukkan jika perlu.

public static class EnumExtensions
{
    public static bool ContainsFlag(this Enum source, Enum flag)
    {
        var sourceValue = ToUInt64(source);
        var flagValue = ToUInt64(flag);

        return (sourceValue & flagValue) == flagValue;
    }

    public static bool ContainsAnyFlag(this Enum source, params Enum[] flags)
    {
        var sourceValue = ToUInt64(source);

        foreach (var flag in flags)
        {
            var flagValue = ToUInt64(flag);

            if ((sourceValue & flagValue) == flagValue)
            {
                return true;
            }
        }

        return false;
    }

    // found in the Enum class as an internal method
    private static ulong ToUInt64(object value)
    {
        switch (Convert.GetTypeCode(value))
        {
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
                return (ulong)Convert.ToInt64(value, CultureInfo.InvariantCulture);

            case TypeCode.Byte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
                return Convert.ToUInt64(value, CultureInfo.InvariantCulture);
        }

        throw new InvalidOperationException("Unknown enum type.");
    }
}
Brian Surowiec
sumber
0

jika seseorang membutuhkan IsSet generik (dibuat di luar kotak dengan cepat dapat ditingkatkan), dan atau string ke konversi Enum onfly (yang menggunakan EnumConstraint yang disajikan di bawah):

  public class TestClass
  { }

  public struct TestStruct
  { }

  public enum TestEnum
  {
    e1,    
    e2,
    e3
  }

  public static class TestEnumConstraintExtenssion
  {

    public static bool IsSet<TEnum>(this TEnum _this, TEnum flag)
      where TEnum : struct
    {
      return (((uint)Convert.ChangeType(_this, typeof(uint))) & ((uint)Convert.ChangeType(flag, typeof(uint)))) == ((uint)Convert.ChangeType(flag, typeof(uint)));
    }

    //public static TestClass ToTestClass(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestClass>(_this);
    //}

    //public static TestStruct ToTestStruct(this string _this)
    //{
    //  // #generates compile error  (so no missuse)
    //  return EnumConstraint.TryParse<TestStruct>(_this);
    //}

    public static TestEnum ToTestEnum(this string _this)
    {
      // #enum type works just fine (coding constraint to Enum type)
      return EnumConstraint.TryParse<TestEnum>(_this);
    }

    public static void TestAll()
    {
      TestEnum t1 = "e3".ToTestEnum();
      TestEnum t2 = "e2".ToTestEnum();
      TestEnum t3 = "non existing".ToTestEnum(); // default(TestEnum) for non existing 

      bool b1 = t3.IsSet(TestEnum.e1); // you can ommit type
      bool b2 = t3.IsSet<TestEnum>(TestEnum.e2); // you can specify explicite type

      TestStruct t;
      // #generates compile error (so no missuse)
      //bool b3 = t.IsSet<TestEnum>(TestEnum.e1);

    }

  }

Jika seseorang masih membutuhkan contoh panas untuk membuat kendala pengkodean Enum:

using System;

/// <summary>
/// would be same as EnumConstraint_T&lt;Enum>Parse&lt;EnumType>("Normal"),
/// but writen like this it abuses constrain inheritence on System.Enum.
/// </summary>
public class EnumConstraint : EnumConstraint_T<Enum>
{

}

/// <summary>
/// provides ability to constrain TEnum to System.Enum abusing constrain inheritence
/// </summary>
/// <typeparam name="TClass">should be System.Enum</typeparam>
public abstract class EnumConstraint_T<TClass>
  where TClass : class
{

  public static TEnum Parse<TEnum>(string value)
    where TEnum : TClass
  {
    return (TEnum)Enum.Parse(typeof(TEnum), value);
  }

  public static bool TryParse<TEnum>(string value, out TEnum evalue)
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    evalue = default(TEnum);
    return Enum.TryParse<TEnum>(value, out evalue);
  }

  public static TEnum TryParse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {    
    Enum.TryParse<TEnum>(value, out defaultValue);
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(string value, TEnum defaultValue = default(TEnum))
    where TEnum : struct, TClass // struct is required to ignore non nullable type error
  {
    TEnum result;
    if (Enum.TryParse<TEnum>(value, out result))
      return result;
    return defaultValue;
  }

  public static TEnum Parse<TEnum>(ushort value)
  {
    return (TEnum)(object)value;
  }

  public static sbyte to_i1<TEnum>(TEnum value)
  {
    return (sbyte)(object)Convert.ChangeType(value, typeof(sbyte));
  }

  public static byte to_u1<TEnum>(TEnum value)
  {
    return (byte)(object)Convert.ChangeType(value, typeof(byte));
  }

  public static short to_i2<TEnum>(TEnum value)
  {
    return (short)(object)Convert.ChangeType(value, typeof(short));
  }

  public static ushort to_u2<TEnum>(TEnum value)
  {
    return (ushort)(object)Convert.ChangeType(value, typeof(ushort));
  }

  public static int to_i4<TEnum>(TEnum value)
  {
    return (int)(object)Convert.ChangeType(value, typeof(int));
  }

  public static uint to_u4<TEnum>(TEnum value)
  {
    return (uint)(object)Convert.ChangeType(value, typeof(uint));
  }

}

semoga ini membantu seseorang.

Tenaga surya
sumber
0

Saya hanya ingin menambahkan Enum sebagai batasan umum.

Meskipun ini hanya untuk metode pembantu kecil menggunakan ExtraConstraints terlalu banyak overhead bagi saya.

Saya memutuskan untuk hanya membuat structbatasan dan menambahkan pemeriksaan runtime IsEnum. Untuk mengonversi variabel dari T ke Enum, saya melemparkannya ke objek terlebih dahulu.

    public static Converter<T, string> CreateConverter<T>() where T : struct
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("Given Type is not an Enum");
        return new Converter<T, string>(x => ((Enum)(object)x).GetEnumDescription());
    }
Jürgen Steinblock
sumber