Kennt jemand einen guten Workaround für das Fehlen einer generischen Enum-Einschränkung?

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

  •  08-06-2019
  •  | 
  •  

Frage

Was ich tun möchte, ist etwa Folgendes:Ich habe Aufzählungen mit kombinierten markierten 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;
    }
}

Dann könnte ich also Folgendes tun:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

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

Leider gibt es in C# generische Einschränkungen, bei denen es keine Enum-Einschränkungen gibt, sondern nur Klassen und Strukturen.C# erkennt Aufzählungen nicht als Strukturen (obwohl es sich um Werttypen handelt), daher kann ich solche Erweiterungstypen nicht hinzufügen.

Kennt jemand einen Workaround?

War es hilfreich?

Lösung

BEARBEITEN:Dies ist jetzt in Version 0.0.0.2 von UnconstrainedMelody verfügbar.

(Wie auf meiner Anfrage Blogbeitrag über Enum-Einschränkungen.Ich habe die grundlegenden Fakten unten aufgeführt, um eine eigenständige Antwort zu erhalten.)

Die beste Lösung ist, darauf zu warten, dass ich es einbinde Uneingeschränkte Melodie1.Dies ist eine Bibliothek, die C#-Code mit „falschen“ Einschränkungen akzeptiert, z

where T : struct, IEnumConstraint

und verwandelt es in

where T : struct, System.Enum

über einen Postbuild-Schritt.

Es sollte nicht zu schwer sein, es zu schreiben IsSet...obwohl für beides gesorgt Int64-basiert und UInt64-basierte Flags könnten der schwierige Teil sein.(Ich rieche, dass einige Hilfsmethoden auftauchen, die es mir im Grunde ermöglichen, jede Flag-Enumeration so zu behandeln, als ob sie einen Basistyp von hätte UInt64.)

Welches Verhalten würden Sie sich wünschen, wenn Sie anrufen?

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

?Sollte es das überprüfen alle die angegebenen Flags gesetzt sind?Das wäre meine Erwartung.

Ich werde versuchen, das heute Abend auf dem Heimweg zu machen ...Ich hoffe, dass ich einen kurzen Überblick über nützliche Enum-Methoden bekomme, um die Bibliothek schnell auf einen brauchbaren Standard zu bringen, und mich dann etwas entspannen kann.

BEARBEITEN:Ich bin mir nicht sicher IsSet als Name übrigens.Optionen:

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

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


1 oder natürlich als Patch einreichen ...

Andere Tipps

Darren, das würde funktionieren, wenn die Typen spezifische 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;
}

Ab C# 7.3 gibt es jetzt eine integrierte Möglichkeit, Enum-Einschränkungen hinzuzufügen:

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

Quelle: https://docs.microsoft.com/en-us/dotnet/csharp/lingual-reference/keywords/where-generic-type-constraint

Eigentlich ist es möglich, mit einem hässlichen Trick.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 du willst, kannst du geben Enums<Temp> ein privater Konstruktor und eine öffentliche verschachtelte abstrakte geerbte Klasse mit Temp als Enum, um geerbte Versionen für Nicht-Aufzählungen zu verhindern.

Sie können dies mit IL Weaving und erreichen ExtraConstraints

Ermöglicht Ihnen, diesen Code zu schreiben

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

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

Ich mache es so, dass ich eine Strukturbeschränkung setze und dann überprüfe, ob T zur Laufzeit eine Aufzählung ist.Dadurch wird das Problem nicht vollständig behoben, aber etwas reduziert

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

Mithilfe Ihres Originalcodes können Sie innerhalb der Methode auch mithilfe von Reflection 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;
    }
}

Hier ist ein Code, den ich gerade erstellt habe und der scheinbar so funktioniert, wie Sie es möchten, ohne dass Sie etwas allzu Verrücktes tun müssen.Es ist nicht nur auf Aufzählungen beschränkt, die als Flags festgelegt sind, sondern es kann bei Bedarf immer eine Prüfung vorgenommen 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.");
    }
}

Wenn jemand ein generisches IsSet benötigt (das sofort einsatzbereit erstellt wurde, könnte verbessert werden) und/oder eine String-zu-Enum-Onfly-Konvertierung (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 Beispiel benötigt, um eine Enum-Coding-Einschrä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));
  }

}

Ich hoffe, das hilft jemandem.

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

Dabei handelt es sich lediglich um eine kleine Hilfsmethode ExtraConstraints ist mir etwas zu viel Aufwand.

Ich habe beschlossen, einfach eine zu erstellen struct Einschränkung und fügen Sie eine Laufzeitprüfung für hinzu IsEnum.Um eine Variable von T in 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());
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top