Bir enum genel kısıtlamasının olmaması için iyi bir geçici çözüm bilen var mı?


90

Yapmak istediğim şey şuna benzer: Birleşik bayraklı değerlere sahip numaralandırmalarım var.

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;
    }
}

O zaman yapabilirdim:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

Ne yazık ki, kısıtlamaların numaralandırma kısıtlamasının olmadığı, yalnızca sınıf ve yapı türünün olduğu C # geneldir. C #, numaralandırmaları yapı olarak görmüyor (değer türü olsalar bile) bu yüzden böyle uzantı türleri ekleyemiyorum.

Bir geçici çözüm bilen var mı?


2
Keith: UnconstrainedMelody'nin 0.0.0.2 sürümünü indirin - HasAll ve HasAny'yi uyguladım. Zevk almak.
Jon Skeet

"C # numaralandırmaları yapı olarak görmüyor" derken neyi kastediyorsunuz? Enum türlerini, structyalnızca ince sınırlandırılmış tür parametreleri olarak kullanabilirsiniz .
Timwi

Bu makaleyi buradan kontrol edin: codeproject.com/KB/cs/ExtendEnum.aspx 'IsValidEnumValue' veya 'IsFlagsEnumDefined' yöntemleri muhtemelen sorunuzun cevabıdır.
dmihailescu

1
Bir gün .net'te yerleşik olarak görmek istiyorsanız, bu uservoice fikrine oy verin .
Matthieu

11
C # 7.3 , enum kısıtlamalarını sunar.
Marc Sigrist

Yanıtlar:


49

DÜZENLEME: Bu artık UnconstrainedMelody'nin 0.0.0.2 sürümünde yayında.

( Enum kısıtlamaları hakkındaki blog gönderimde istendiği gibi . Bağımsız bir yanıt için aşağıya temel gerçekleri ekledim.)

En iyi çözüm, onu UnconstrainedMelody 1'e dahil etmemi beklemektir . Bu, C # kodunu "sahte" kısıtlamalarla alan bir kütüphanedir.

where T : struct, IEnumConstraint

ve onu dönüştürür

where T : struct, System.Enum

bir postbuild adımıyla.

Yazmak çok zor olmasa da IsSet... hem Int64temelli hem de UInt64tabanlı bayraklar için yemek servisi zor kısım olabilir. (Bazı yardımcı yöntemlerin geldiğini kokluyorum, temel olarak herhangi bir bayrak numaralandırmasına temel bir türü varmış gibi davranmama izin veriyor UInt64.)

Ararsan davranışın ne olmasını isterdin

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

? Belirtilen tüm bayrakların ayarlandığını kontrol etmeli mi? Bu benim beklentim olurdu.

Bunu bu gece eve giderken yapmaya çalışacağım ... Kütüphaneyi hızlı bir şekilde kullanılabilir bir standarda getirmek için kullanışlı numaralandırma yöntemlerine hızlı bir şekilde saldırmayı ve sonra biraz rahatlamayı umuyorum.

DÜZENLEME: Bu IsSetarada, bir isim olarak emin değilim . Seçenekler:

  • İçerir
  • İçerir
  • HasFlag (veya HasFlags)
  • IsSet (bu kesinlikle bir seçenek)

Düşünceler hoş karşılanır. Eminim her şey değişmeden önce biraz zaman alacak ...


1 veya bir yama olarak gönderin, tabii ki ...



1
Ya da aslında daha basit HasAny () ve HasAll ()
Keith

1
Evet, bunun daha da iyi olduğuna katılıyorum. colors.HasAny(Colors.Red | Colors.Blue)okunabilir bir koda benziyor. =)
Blixt

1
Evet, HasAny ve HasAll'ı da seviyorum. Bununla gidecek.
Jon Skeet

5
C # 7.3'ten (Mayıs 2018'de yayınlandı) beri, kısıtlamayı kullanmak mümkündür where T : System.Enum. Bu zaten ileti dizisinin başka bir yerinde yazılmıştır; Burada tekrar edeceğimi düşündüm.
Jeppe Stig Nielsen


16

Darren, bu türler belirli numaralandırmalar olsaydı işe yarar - genel numaralandırmaların çalışabilmesi için, boolean matematiğini yapmak için onları ints'e (veya daha büyük olasılıkla uint) çevirmen gerekir:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

1
Ve gülünç sayıda bayrağınız varsa, bağımsız değişkenlerde GetTypeCode () ve Convert.ToUint64 ()
Kit

Harika, "Enum" ve Convert.ToUInt32ben kombinasyonu başka hiçbir yerde bulamadık. AFAIK, VB'de de çalışan tek düzgün Pre-Net-4 çözümü. BTW, matchTobirden fazla bayrak biti varsa, != 0ile değiştirin == Convert.ToUInt32(matchTo).
ToolmakerSteve

1
Bu Not Convert.ToUInt32kullanacağı bir enum ile kullanılan Convert.ToUInt32(object)CLR ilk sonra geçirmeden önce, bu değerler kutu, bunun anlamı aşırı ToUInt32yöntemi. Çoğu durumda bu önemli değildir, ancak saniyede milyonlarca numarayı ayrıştırmak için böyle bir şey kullanıyorsanız, GC'yi oldukça meşgul tutacağınızı bilmek güzel.
Groo

10

Aslında çirkin bir numara ile mümkün. Ancak, uzatma yöntemleri için kullanılamaz.

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")

İsterseniz, numaralandırma olmayanlar için miras alınan sürümleri önlemek Enums<Temp>için özel bir kurucu ve Tempas ile genel iç içe geçmiş soyut devralınan sınıf Enumverebilirsiniz.


8

Bunu IL Weaving ve ExtraConstraints kullanarak başarabilirsiniz.

Bu kodu yazmanıza izin verir

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

Ne derlenir

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

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

7

C # 7.3'ten itibaren, genel türlerde Enum kısıtlamasını kullanabilirsiniz:

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

Null yapılabilir bir numaralandırma kullanmak istiyorsanız, orjinal yapı kısıtlamasını bırakmalısınız:

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

4

Bu, orijinal soruyu yanıtlamaz, ancak artık .NET 4'te Enum.HasFlag adlı, örneğinizde yapmaya çalıştığınız şeyi yapan bir yöntem vardır.


Bu noktada, herkesin .NET 4 (veya üzeri) kullanması gerektiği ve bu nedenle birlikte hacklemeye çalışmak yerine bu yöntemi kullanmaları gerektiği için oy verildi.
CptRobby

Olumlu oy verildi. Ancak çözümleri, argümanın kutulanmasını kullanır flag. .NET 4.0 artık beş yaşında.
Jeppe Stig Nielsen

3

Bunu yapma şeklim, bir yapı kısıtlaması koymak, ardından T'nin çalışma zamanında bir enum olduğunu kontrol etmektir. Bu, sorunu tamamen ortadan kaldırmaz, ancak bir şekilde azaltır.


7
burada T: struct, IComparable, IFormattable, IConvertible - bu numaralandırmaya en yakın olanıdır :)
Kit

1

Orijinal kodunuzu kullanarak, yöntemin içinde, T'nin bir enum olduğunu test etmek için yansımayı da kullanabilirsiniz:

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;
    }
}

2
Teşekkürler, ancak bu bir derleme zamanı sorununu (nerede kısıtlaması) bir çalışma zamanı sorununa (istisnanız) dönüştürür. Ayrıca, onlarla herhangi bir şey yapmadan önce girişleri girişlere dönüştürmeniz gerekir.
Keith

1

İşte az önce yaptığım, çok çılgınca bir şey yapmak zorunda kalmadan istediğiniz gibi çalışan bazı kodlar. Yalnızca Bayrak olarak ayarlanan numaralandırmalarla sınırlı değildir, ancak gerekirse her zaman bir kontrol konulabilir.

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.");
    }
}

0

eğer birisi genel IsSet'e (anında geliştirilebilir) ve veya Enum onfly dönüşümüne (aşağıda sunulan EnumConstraint kullanan) dizeye ihtiyaç duyarsa:

  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);

    }

  }

Birinin Enum kodlama kısıtlaması oluşturmak için sıcak örneğe ihtiyacı varsa:

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));
  }

}

umarım bu birine yardımcı olur.


0

Enum'u genel bir kısıtlama olarak eklemek istedim.

Bu sadece küçük bir yardımcı yöntem için olsa da, kullanmak ExtraConstraintsbenim için biraz fazla ek yük.

Sadece bir structkısıtlama oluşturmaya ve bir çalışma zamanı kontrolü eklemeye karar verdim IsEnum. Bir değişkeni T'den Enum'a dönüştürmek için önce onu nesneye dönüştürüyorum.

    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());
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.