Kennt jemand eine gute Problemumgehung für das Fehlen einer generischen Enum-Einschränkung?


89

Was ich tun möchte, ist ungefähr so: Ich habe Aufzählungen mit kombinierten gekennzeichneten Werten.

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

Also könnte ich tun:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

Leider ist C # generisch, wo Einschränkungen keine Aufzählungsbeschränkung haben, nur Klasse und Struktur. C # sieht Enums nicht als Strukturen (obwohl es sich um Werttypen handelt), daher kann ich solche Erweiterungstypen nicht hinzufügen.

Kennt jemand eine Problemumgehung?


2
Keith: Download Version 0.0.0.2 von UnconstrainedMelody - Ich habe HasAll und HasAny implementiert. Genießen.
Jon Skeet

Was meinst du mit "C # sieht Enums nicht als Strukturen"? Sie können Aufzählungstypen als Typparameter verwenden, die auf einwandfrei beschränkt sind struct.
Timwi

Überprüfen Sie diesen Artikel hier: codeproject.com/KB/cs/ExtendEnum.aspx Die Methoden 'IsValidEnumValue' oder 'IsFlagsEnumDefined' sind wahrscheinlich die Antwort auf Ihre Frage.
Dmihailescu

1
Stimmen Sie für diese Uservoice-Idee ab , wenn Sie möchten, dass sie eines Tages in .net integriert ist.
Matthieu

11
C # 7.3 führt Enum-Einschränkungen ein.
Marc Sigrist

Antworten:


48

BEARBEITEN: Dies ist jetzt live in Version 0.0.0.2 von UnconstrainedMelody.

(Wie in meinem Blog-Beitrag zu Aufzählungsbeschränkungen angefordert . Ich habe die folgenden grundlegenden Fakten hinzugefügt, um eine eigenständige Antwort zu erhalten.)

Die beste Lösung ist, darauf zu warten, dass ich es in UnconstrainedMelody 1 einbinde . Dies ist eine Bibliothek, die C # -Code mit "falschen" Einschränkungen wie z

where T : struct, IEnumConstraint

und verwandelt es in

where T : struct, System.Enum

über einen Postbuild-Schritt.

Es sollte nicht zu schwer zu schreiben sein IsSet... obwohl es schwierig sein könnte, sowohl auf der Int64Basis als auch auf der UInt64Basis von Flags zu arbeiten. (Ich rieche einige Hilfsmethoden, die es mir ermöglichen, alle Flaggen so zu behandeln, als ob sie einen Basistyp hättenUInt64 .)

Was möchten Sie, wenn Sie anrufen?

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

? Sollte überprüft werden, ob alle angegebenen Flags gesetzt sind? Das wäre meine Erwartung.

Ich werde versuchen, dies heute Abend auf dem Heimweg zu tun ... Ich hoffe, einen kurzen Einblick in nützliche Aufzählungsmethoden zu bekommen, um die Bibliothek schnell auf einen brauchbaren Standard zu bringen, und mich dann ein wenig zu entspannen.

EDIT: Ich bin mir IsSetübrigens nicht sicher , wie es heißt. Optionen:

  • Beinhaltet
  • Enthält
  • HasFlag (oder HasFlags)
  • IsSet (es ist sicherlich eine Option)

Gedanken willkommen. Ich bin mir sicher, dass es eine Weile dauern wird, bis irgendetwas in Stein gemeißelt ist ...


1 oder als Patch einreichen, natürlich ...


1
Sie mussten gehen und PostSharp LOL erwähnen: o postsharp.org/blog/generic-constraints-for-enums-and-delegates
Sam Harwell

1
Oder eigentlich einfacher HasAny () und HasAll ()
Keith

1
Ja, ich stimme zu, das ist noch besser. colors.HasAny(Colors.Red | Colors.Blue)sieht aus wie sehr lesbarer Code. =)
Blixt

1
Ja, ich mag auch HasAny und HasAll. Wird damit gehen.
Jon Skeet

5
Seit C # 7.3 (veröffentlicht im Mai 2018) ist es möglich, die Einschränkung zu verwenden where T : System.Enum. Dies wurde bereits an anderer Stelle im Thread geschrieben; Ich dachte nur, ich würde es hier wiederholen.
Jeppe Stig Nielsen


16

Darren, das würde funktionieren, wenn die Typen bestimmte Aufzählungen wären - damit allgemeine Aufzählungen funktionieren, müssen Sie sie in Ints (oder wahrscheinlicher Uint) umwandeln, um die boolesche Mathematik durchzuführen:

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

1
Und wenn Sie eine lächerliche Anzahl von Flags haben, können Sie GetTypeCode () für die Argumente und Convert.ToUint64 ()
Kit

Genial, die Kombination von 'Enum' und Convert.ToUInt32ich habe nirgendwo anders gefunden. AFAIK, es ist die einzige anständige Pre-Net-4-Lösung, die auch in VB funktioniert. Übrigens, wenn matchTomöglicherweise mehrere Flag-Bits vorhanden sind, ersetzen Sie diese != 0durch == Convert.ToUInt32(matchTo).
ToolmakerSteve

1
Beachten Sie, dass bei Convert.ToUInt32Verwendung mit einer Aufzählung die Convert.ToUInt32(object)Überladung verwendet wird. Dies bedeutet, dass CLR diese Werte zuerst einschließt, bevor sie an die ToUInt32Methode übergeben werden. In den meisten Fällen spielt dies keine Rolle, aber es ist gut zu wissen, dass Sie den GC ziemlich beschäftigt halten, wenn Sie so etwas verwenden, um Millionen von Aufzählungen pro Sekunde zu analysieren.
Groo

10

Eigentlich ist es mit einem hässlichen Trick möglich. Es kann jedoch nicht für Erweiterungsmethoden verwendet werden.

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

Wenn Sie möchten, können Sie Enums<Temp>einen privaten Konstruktor und eine öffentlich verschachtelte abstrakte geerbte Klasse mit Tempas Enumangeben, um geerbte Versionen für Nicht-Enums zu verhindern.


8

Sie können dies mit IL Weaving und ExtraConstraints erreichen

Ermöglicht das Schreiben dieses Codes

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

Was wird kompiliert

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

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

6

Ab C # 7.3 können Sie die Enum-Einschränkung für generische Typen verwenden:

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

Wenn Sie eine Nullable-Aufzählung verwenden möchten, müssen Sie die ursprüngliche Strukturbeschränkung beibehalten:

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

4

Dies beantwortet nicht die ursprüngliche Frage, aber in .NET 4 gibt es jetzt eine Methode namens Enum.HasFlag, die genau das tut, was Sie in Ihrem Beispiel versuchen


Upvoted, da zu diesem Zeitpunkt fast jeder .NET 4 (oder höher) verwenden sollte und daher diese Methode verwenden sollte, anstatt zu versuchen, sie zusammen zu hacken.
CptRobby

Upvoted. Ihre Lösung verwendet jedoch das Boxen des Arguments flag. .NET 4.0 ist jetzt fünf Jahre alt.
Jeppe Stig Nielsen

3

So wie ich es mache, wird eine Strukturbeschränkung gesetzt und dann überprüft, ob T zur Laufzeit eine Aufzählung ist. Dies beseitigt das Problem nicht vollständig, reduziert es jedoch etwas


7
Dabei ist T: struct, IComparable, IFormattable, IConvertible - dies ist der nächste Punkt, den Sie erreichen können :)
Kit

1

Mit Ihrem Originalcode können Sie innerhalb der Methode auch die Reflexion verwenden, um zu testen, ob T eine Aufzählung ist:

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
Vielen Dank, aber das verwandelt ein Problem mit der Kompilierungszeit (die where-Einschränkung) in ein Laufzeitproblem (Ihre Ausnahme). Außerdem müssten Sie die Eingaben noch in Ints konvertieren, bevor Sie etwas damit tun können.
Keith

1

Hier ist ein Code, den ich gerade erstellt habe und der so zu funktionieren scheint, wie Sie es möchten, ohne etwas zu Verrücktes tun zu müssen. Es ist nicht nur auf Aufzählungen beschränkt, die als Flags festgelegt sind, sondern es kann bei Bedarf immer ein Scheck eingefügt werden.

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

Wenn jemand generisches IsSet benötigt (das sofort im laufenden Betrieb erstellt werden könnte, könnte verbessert werden) und / oder eine Konvertierung von Zeichenfolge in Enum onfly (die die unten dargestellte EnumConstraint verwendet):

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

    }

  }

Wenn jemand noch ein heißes Beispiel benötigt, um eine Enum-Codierungsbeschränkung zu erstellen:

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

}

hoffe das hilft jemandem.


0

Ich wollte nur Enum als generische Einschränkung hinzufügen.

Dies ist zwar nur für eine winzige Hilfsmethode gedacht ExtraConstraints ist die für mich etwas zu viel Aufwand.

Ich habe mich entschieden, einfach eine structEinschränkung zu erstellen und eine Laufzeitprüfung für hinzuzufügen IsEnum. Um eine Variable von T nach Enum zu konvertieren, habe ich sie zuerst in ein Objekt umgewandelt.

    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());
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.