누구나 열거 형 제네릭 제약 조건이없는 경우 좋은 해결 방법을 알고 있습니까?


90

내가 원하는 것은 다음과 같습니다. 플래그가 지정된 값이 결합 된 열거 형이 있습니다.

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 #은 열거 형을 구조체로 보지 않으므로 (값 유형 임에도 불구하고) 이와 같은 확장 유형을 추가 할 수 없습니다.

누구든지 해결 방법을 알고 있습니까?


2
Keith : UnconstrainedMelody 0.0.0.2 버전 다운로드-HasAll 및 HasAny를 구현했습니다. 즐겨.
Jon Skeet

"C #에서 열거 형을 구조체로 보지 않는다"는 의미는 무엇입니까? enum 유형을 struct잘 제한되는 유형 매개 변수로 사용할 수 있습니다 .
Timwi

여기에이 문서를 확인하십시오 codeproject.com/KB/cs/ExtendEnum.aspx 'IsValidEnumValue'또는 'IsFlagsEnumDefined'방법은 아마 당신의 질문에 대한 대답입니다.
dmihailescu

1
언젠가 .net에 내장 된 것을보고 싶다면uservoice 아이디어에 투표 하십시오.
Matthieu 2014

11
C # 7.3 에는 열거 형 제약 조건이 도입되었습니다.
Marc Sigrist 2018

답변:


49

편집 : 이것은 이제 UnconstrainedMelody의 0.0.0.2 버전에 있습니다.

( enum 제약 조건에 대한블로그 게시물 에서 요청한대로 . 독립형 답변을 위해 아래 기본 사실을 포함했습니다.)

가장 좋은 해결책은 UnconstrainedMelody 1 에 포함시킬 때까지 기다리는 것입니다 . 이것은 다음과 같은 "가짜"제약 조건이있는 C # 코드를 사용하는 라이브러리입니다.

where T : struct, IEnumConstraint

그리고 그것을

where T : struct, System.Enum

빌드 후 단계를 통해.

작성하기가 너무 어렵지 않아야 IsSet합니다. Int64기반 및 UInt64기반 플래그 를 모두 처리 하는 것이 까다로운 부분이 될 수 있지만. (기본적으로 UInt64.

전화를 걸면 어떤 행동을 원하십니까?

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

? 지정된 모든 플래그가 설정 되었는지 확인해야합니까 ? 그것이 내 기대입니다.

오늘 밤 집으로가는 길에이 작업을 수행해 보겠습니다. 라이브러리를 사용 가능한 표준에 빠르게 맞추고 잠시 휴식을 취하기 위해 유용한 enum 메서드에 대한 빠른 블리츠를 기대하고 있습니다.

편집하다 : 그건 IsSet그렇고, 이름이 확실하지 않습니다 . 옵션 :

  • 포함
  • 포함
  • HasFlag (또는 HasFlags)
  • IsSet (확실히 옵션 임)

생각을 환영합니다. 어차피 결정이 내려지기까지는 시간이 좀 걸릴 거라고 확신 해요 ...


1 또는 패치로 제출하십시오.


1
당신은 가서 PostSharp LOL을 언급해야했습니다. : o postsharp.org/blog/generic-constraints-for-enums-and-delegates
Sam Harwell

1
또는 실제로 더 간단한 HasAny () 및 HasAll ()
Keith

1
예, 더 낫다는 데 동의합니다. colors.HasAny(Colors.Red | Colors.Blue)매우 읽기 쉬운 코드처럼 보입니다. =)
Blixt

1
네, HasAny와 HasAll도 좋아합니다. 그것과 함께 갈 것입니다.
Jon Skeet

5
C # 7.3 (2018 년 5 월 출시)부터 제약 조건을 사용할 수 있습니다 where T : System.Enum. 이것은 스레드의 다른 곳에서 이미 작성되었습니다. 여기서 반복하겠다고 생각했습니다.
Jeppe Stig Nielsen


16

Darren, 유형이 특정 열거 형이면 작동합니다. 일반 열거 형이 작동하려면 부울 수학을 수행하기 위해 int (또는 더 가능성이 높은 uint)로 캐스팅해야합니다.

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

1
그리고 엄청나게 많은 수의 플래그가있는 경우 인수 및 Convert.ToUint64 ()
Kit

'Enum'과 Convert.ToUInt32나는 다른 곳에서 찾지 못했습니다. AFAIK, VB에서도 작동하는 유일한 Pre-Net-4 솔루션입니다. 경우 BTW, matchTo힘 여러 플래그 비트를 가지고, 다음 교체 != 0와 함께 == Convert.ToUInt32(matchTo).
ToolmakerSteve

1
참고 Convert.ToUInt32사용합니다 열거와 함께 사용 Convert.ToUInt32(object)CLR 처음에 다음 전달하기 전에이 값을 상자 것을 의미, 과부하 ToUInt32방법. 대부분의 경우 이것은 중요하지 않지만 이와 같은 것을 사용하여 초당 수백만 개의 열거 형을 구문 분석하는 경우 GC를 다소 바쁘게 유지한다는 것을 아는 것이 좋습니다.
Groo

10

사실 추악한 속임수로 가능합니다. 그러나 확장 방법에는 사용할 수 없습니다.

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

원하는 경우 as를 사용 Enums<Temp>하여 개인 생성자 및 공용 중첩 추상 상속 클래스를 제공하여 비 열거 형에 대한 상속 된 버전을 방지 할 수 있습니다.TempEnum


8

IL Weaving 및 ExtraConstraints를 사용하여이를 달성 할 수 있습니다.

이 코드를 작성할 수 있습니다.

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

6

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

4

이것은 원래 질문에 대답하지 않지만 이제 .NET 4에는 Enum.HasFlag 라는 메서드가 있습니다.이 메서드는 예제에서 수행하려는 작업을 수행합니다.


이 시점에서 대부분의 모든 사람이 .NET 4 이상을 사용하고 있으므로 함께 해킹하는 대신이 방법을 사용해야하기 때문에 찬성했습니다.
CptRobby

찬성. 그러나 그들의 솔루션은 논쟁의 권투를 사용합니다 flag. .NET 4.0은 이제 5 년이되었습니다.
Jeppe Stig Nielsen

3

내가하는 방법은 구조체 제약 조건을 넣은 다음 T가 런타임에 열거 형인지 확인하는 것입니다. 이것은 문제를 완전히 제거하지는 않지만 다소 감소시킵니다.


7
여기서 T : struct, IComparable, IFormattable, IConvertible-열거 형에 가장 가까운 값입니다. :)
Kit

1

원래 코드를 사용하여 메서드 내에서 리플렉션을 사용하여 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;
    }
}

2
감사합니다.하지만 컴파일 시간 문제 (where 제약 조건)가 런타임 문제 (예외)로 바뀝니다. 또한 입력을 int로 변환해야 어떤 작업을 수행 할 수 있습니다.
Keith

1

다음은 너무 미친 짓을하지 않고도 원하는대로 작동하는 것처럼 보이는 코드입니다. 플래그로 설정된 열거 형에만 국한되지는 않지만 필요한 경우 항상 체크를 넣을 수 있습니다.

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

누군가 일반 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));
  }

}

이것이 누군가를 돕기를 바랍니다.


0

Enum을 일반 제약 조건으로 추가하고 싶었습니다.

이것은 단지 사용하는 작은 도우미 방법이지만 ExtraConstraints 것은 나에게 너무 많은 오버 헤드입니다.

struct제약 조건을 만들고 .NET Framework에 대한 런타임 검사를 추가 하기로 결정했습니다 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());
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.