هل يعرف أحد حلاً جيدًا لعدم وجود قيد عام للتعداد؟

StackOverflow https://stackoverflow.com/questions/7244

  •  08-06-2019
  •  | 
  •  

سؤال

ما أريد القيام به هو شيء من هذا القبيل:لدي تعدادات ذات قيم مدمجة.

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

إذن يمكنني أن أفعل:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

لسوء الحظ، لغة C# عامة حيث لا تحتوي القيود على قيود التعداد، فقط الفئة والبنية.لا يرى C# التعدادات كبنيات (على الرغم من أنها أنواع قيمة) لذا لا يمكنني إضافة أنواع امتداد مثل هذا.

لا أحد يعرف الحل؟

هل كانت مفيدة؟

المحلول

يحرر:هذا موجود الآن في الإصدار 0.0.0.2 من UnconstrainedMelody.

(كما هو مطلوب في بلدي مشاركة مدونة حول قيود التعداد.لقد قمت بتضمين الحقائق الأساسية أدناه للحصول على إجابة مستقلة.)

الحل الأفضل هو الانتظار حتى أقوم بإدراجه ميلودي غير مقيد1.هذه مكتبة تأخذ كود C# مع قيود "زائفة" مثل

where T : struct, IEnumConstraint

ويحوله الى

where T : struct, System.Enum

عبر خطوة ما بعد البناء.

لا ينبغي أن يكون من الصعب جدا الكتابة IsSet...على الرغم من تقديم الطعام لكليهما Int64- القائم و UInt64يمكن أن تكون الأعلام المستندة إلى الجزء الصعب.(أشم رائحة بعض الأساليب المساعدة القادمة، مما يسمح لي بشكل أساسي بالتعامل مع أي علامات تعداد كما لو كان لها نوع أساسي من UInt64.)

ماذا تريد أن يكون السلوك إذا اتصلت

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

؟هل ينبغي التحقق من ذلك الجميع يتم تعيين الأعلام المحددة؟وهذا سيكون توقعي.

سأحاول القيام بذلك في طريق عودتي إلى المنزل الليلة.آمل أن أحصل على نظرة سريعة على طرق التعداد المفيدة للارتقاء بالمكتبة إلى مستوى قابل للاستخدام بسرعة، ثم الاسترخاء قليلاً.

يحرر:لست متأكدا من ذلك IsSet كاسم بالمناسبة.خيارات:

  • يشمل
  • يتضمن
  • HasFlag (أو HasFlags)
  • IsSet (إنه بالتأكيد خيار)

نرحب بالأفكار.أنا متأكد من أنه سيستغرق بعض الوقت قبل أن يتم وضع أي شيء في الحجر على أي حال ...


1 أو إرسالها كتصحيح، بطبيعة الحال.

نصائح أخرى

دارين، قد ينجح هذا إذا كانت الأنواع عبارة عن تعدادات محددة - لكي تعمل التعدادات العامة، عليك إرسالها إلى ints (أو على الأرجح uint) للقيام بالرياضيات المنطقية:

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

اعتبارًا من الإصدار C# 7.3، توجد الآن طريقة مدمجة لإضافة قيود التعداد:

public class UsingEnum<T> where T : System.Enum { }

مصدر: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint

في الواقع، هذا ممكن، مع خدعة قبيحة.ومع ذلك، لا يمكن استخدامه لطرق التمديد.

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

إذا كنت تريد، يمكنك أن تعطي Enums<Temp> منشئ خاص وفئة مجردة متداخلة عامة مع Temp مثل Enum, ، لمنع الإصدارات الموروثة لغير التعدادات.

يمكنك تحقيق ذلك باستخدام IL Weaving و القيود الإضافية

يسمح لك بكتابة هذا الرمز

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

ما يتم تجميعه

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

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

هذا لا يجيب على السؤال الأصلي، ولكن توجد الآن طريقة في .NET 4 تسمى Enum.HasFlag الذي يفعل ما تحاول القيام به في المثال الخاص بك

الطريقة التي أفعل بها ذلك هي وضع قيد هيكلي، ثم التحقق من أن T عبارة عن تعداد في وقت التشغيل.وهذا لا يقضي على المشكلة بشكل كامل، لكنه يقلل منها إلى حد ما

اعتبارًا من الإصدار C# 7.3، يمكنك استخدام قيد Enum على الأنواع العامة:

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

إذا كنت تريد استخدام التعداد Nullable، فيجب عليك ترك قيد البنية الأصلية:

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

باستخدام الكود الأصلي، داخل الطريقة يمكنك أيضًا استخدام الانعكاس لاختبار أن T عبارة عن تعداد:

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

إليك بعض التعليمات البرمجية التي قمت بإعدادها للتو والتي يبدو أنها تعمل كما تريد دون الحاجة إلى القيام بأي شيء مجنون.لا يقتصر الأمر على التعدادات التي تم تعيينها كعلامات فقط، ولكن يمكن دائمًا إجراء فحص إذا لزم الأمر.

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

إذا كان شخص ما يحتاج إلى IsSet عام (يمكن تحسينه والذي تم إنشاؤه خارج الصندوق بشكل سريع)، أو تحويل سلسلة إلى Enum onfly (الذي يستخدم EnumConstraint الموضح أدناه):

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

    }

  }

إذا كان شخص ما لا يزال بحاجة إلى مثال ساخن لإنشاء قيد ترميز 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));
  }

}

أمل أن هذا يساعد شخصاما.

أردت فقط إضافة Enum كقيد عام.

في حين أن هذا مجرد طريقة مساعدة صغيرة تستخدم ExtraConstraints هو الكثير من النفقات العامة بالنسبة لي.

قررت أن أقوم فقط بإنشاء ملف struct القيد وإضافة فحص وقت التشغيل لـ IsEnum.لتحويل متغير من T إلى Enum، قمت بتحويله إلى الكائن أولاً.

    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());
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top