c# - 상속 - unity enum generic




T를 Enum으로 제한하는 일반 메소드 생성 (14)

나는 Enum.Parse 을 확장하는 함수를 Enum.Parse 있다.

  • Enum 값을 찾을 수없는 경우 기본값을 파싱 할 수 있습니다.
  • 대소 문자를 구분하지 않습니다.

그래서 나는 다음과 같이 썼다.

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

Error Constraint가 System.Enum 특수 클래스가 될 수 없습니다.

공평하지만, 일반 열거 형을 허용하는 해결 방법이 있습니까, 아니면 Parse 함수를 모방하여 속성으로 유형을 전달해야하므로 추한 복싱 요구 사항을 코드에 적용해야합니다.

편집 아래의 모든 제안을 주셔서 감사 드리며 감사드립니다.

정착했다 (나는 대소 문자를 구분하지 않기 위해 루프를 떠났다. XML을 파싱 할 때 이것을 사용하고있다)

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

편집 : (2015 년 2 월 16 일) Julien Lebosquain은 최근 MSIL 또는 F #에서 컴파일러가 적용하는 타입 안전 일반 솔루션을 게시 했습니다. 이 제품은보기 좋을 것이고 upvote입니다. 솔루션이 페이지 위로 올라가는 경우이 편집을 제거합니다.


C # ≥ 7.3

C # 7.3 (Visual Studio 2017 ≥ v15.7에서 사용 가능)부터이 코드는 이제 완전히 유효합니다.

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

C # ≤ 7.2

제약 상속을 남용하여 실제 컴파일러가 enum 제약을 적용 할 수 있습니다. 다음 코드는 classstruct 제약 조건을 동시에 지정합니다.

public abstract class EnumClassUtils<TClass>
where TClass : class
{

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

}

public class EnumUtils : EnumClassUtils<Enum>
{
}

용법:

EnumUtils.Parse<SomeEnum>("value");

참고 :이 내용은 C # 5.0 언어 사양에 명시되어 있습니다.

유형 매개 변수 S가 유형 매개 변수 T에 종속되면 : [...] S가 값 유형 제약 조건을 갖고 T가 참조 유형 제약 조건을 갖는 것은 유효합니다. 효과적으로 T는 System.Object, System.ValueType, System.Enum 및 모든 인터페이스 유형으로 T를 제한합니다.


이 기능은 마침내 C # 7.3에서 지원됩니다!

다음 스 니펫 ( dotnet 샘플 )은 사용법을 보여줍니다.

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

C # 프로젝트의 언어 버전을 버전 7.3으로 설정하십시오.

원문 아래의 답변 :

나는 게임에 늦었지만 어떻게 할 수 있는지보기 위해 도전으로 생각했다. C # (또는 VB.NET에서는 가능하지 않지만 F #에서는 아래로 스크롤) 이 가능 하지만 MSIL 에서는 가능합니다 . 나는이 작은 .... 것을 썼다.

// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
       extends [mscorlib]System.Object
{
  .method public static !!T  GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
                                                                                          !!T defaultValue) cil managed
  {
    .maxstack  2
    .locals init ([0] !!T temp,
                  [1] !!T return_value,
                  [2] class [mscorlib]System.Collections.IEnumerator enumerator,
                  [3] class [mscorlib]System.IDisposable disposer)
    // if(string.IsNullOrEmpty(strValue)) return defaultValue;
    ldarg strValue
    call bool [mscorlib]System.String::IsNullOrEmpty(string)
    brfalse.s HASVALUE
    br RETURNDEF         // return default it empty

    // foreach (T item in Enum.GetValues(typeof(T)))
  HASVALUE:
    // Enum.GetValues.GetEnumerator()
    ldtoken !!T
    call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
    callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    stloc enumerator
    .try
    {
      CONDITION:
        ldloc enumerator
        callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        brfalse.s LEAVE

      STATEMENTS:
        // T item = (T)Enumerator.Current
        ldloc enumerator
        callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        unbox.any !!T
        stloc temp
        ldloca.s temp
        constrained. !!T

        // if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        callvirt instance string [mscorlib]System.Object::ToString()
        callvirt instance string [mscorlib]System.String::ToLower()
        ldarg strValue
        callvirt instance string [mscorlib]System.String::Trim()
        callvirt instance string [mscorlib]System.String::ToLower()
        callvirt instance bool [mscorlib]System.String::Equals(string)
        brfalse.s CONDITION
        ldloc temp
        stloc return_value
        leave.s RETURNVAL

      LEAVE:
        leave.s RETURNDEF
    }
    finally
    {
        // ArrayList's Enumerator may or may not inherit from IDisposable
        ldloc enumerator
        isinst [mscorlib]System.IDisposable
        stloc.s disposer
        ldloc.s disposer
        ldnull
        ceq
        brtrue.s LEAVEFINALLY
        ldloc.s disposer
        callvirt instance void [mscorlib]System.IDisposable::Dispose()
      LEAVEFINALLY:
        endfinally
    }

  RETURNDEF:
    ldarg defaultValue
    stloc return_value

  RETURNVAL:
    ldloc return_value
    ret
  }
} 

유효한 C # 인 경우 다음과 같은 함수를 생성합니다.

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

다음 C # 코드를 사용합니다.

using MyThing;
// stuff...
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
    Thing.GetEnumFromString("No", MyEnum.Yes); // returns MyEnum.No
    Thing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.Okay
    Thing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum
}

불행히도 이것은 C # 대신 MSIL로 작성된이 코드 부분을 System.Enum 의해 제약 할 수있는 유일한 부가적인 이점이 있음을 의미 System.Enum . 별도의 어셈블리로 컴파일되기 때문에 일종의 허풍입니다. 그러나 그런 식으로 배포해야한다는 의미는 아닙니다.

.assembly MyThing{} 을 제거하고 다음과 같이 ilasm을 호출합니다.

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

어셈블리 대신 netmodule을 얻습니다.

불행하게도, VS2010 (그리고 이전 버전에서는 분명히)은 netmodule 레퍼런스 추가를 지원하지 않습니다. 따라서 디버깅 할 때 두 개의 별도 어셈블리에 두어야합니다. 어셈블리의 일부로 추가 할 수있는 유일한 방법은 /addmodule:{files} 명령 줄 인수를 사용하여 csc.exe를 직접 실행하는 것입니다. MSBuild 스크립트에서는 그리 고통스럽지 않습니다. 물론, 용감하거나 바보 같은 사람이라면 매번 수동으로 csc를 실행할 수 있습니다. 또한 여러 어셈블리가 액세스해야하기 때문에 더욱 복잡해집니다.

그래서 .Net에서 할 수 있습니다. 여분의 노력을 기울일만한 가치가 있습니까? 음, 그걸 결정하게 내버려 두 겠네.

대안으로 F # 솔루션

추가 크레딧 : enum 에 대한 일반적인 제한은 MSIL : F # 외에 적어도 하나의 다른 .NET 언어에서 가능하다는 것이 밝혀졌습니다.

type MyThing =
    static member GetEnumFromString<'T when 'T :> Enum> str defaultValue: 'T =
        /// protect for null (only required in interop with C#)
        let str = if isNull str then String.Empty else str

        Enum.GetValues(typedefof<'T>)
        |> Seq.cast<_>
        |> Seq.tryFind(fun v -> String.Compare(v.ToString(), str.Trim(), true) = 0)
        |> function Some x -> x | None -> defaultValue

이 도구는 Visual Studio IDE를 완벽하게 지원하는 잘 알려진 언어이므로 유지 관리가 더 쉽지만 솔루션에 별도의 프로젝트가 필요합니다. 그러나, 그것은 자연스럽게 상당히 다른 IL을 생성합니다 (코드 매우 다릅니다) 그리고 다른 외부 라이브러리와 마찬가지로 FSharp.Core 라이브러리에 의존합니다.이 라이브러리는 FSharp.Core 일부가되어야합니다.

여기서는 (MSIL 솔루션과 기본적으로) 동일한 방법으로 사용할 수 있으며, 동의어가있는 구조체에서 올바르게 실패했음을 보여줍니다.

// works, result is inferred to have type StringComparison
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);
// type restriction is recognized by C#, this fails at compile time
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", 42);

Vivek의 솔루션을 재사용 할 수있는 유틸리티 클래스에 캡슐화했습니다. 귀하의 유형에 "T : struct, IConvertible"형식 제약 조건을 정의해야합니다.

using System;

internal static class EnumEnforcer
{
    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",
                typeParameterName,
                methodName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    /// <param name="inputParameterName">Name of the input parameter of this page.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",
                typeParameterName,
                methodName,
                inputParameterName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="exceptionMessage">Message to show in case T is not an enum.</param>
    public static void EnforceIsEnum<T>(string exceptionMessage)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(exceptionMessage);
        }
    }
}

enum 값과 관련된 텍스트와 함께 enum을 사용해야하는 특정 요구 사항이 있습니다. 예를 들어 enum을 사용하여 오류 유형을 지정하면 오류 세부 정보를 설명해야합니다.

public static class XmlEnumExtension
{
    public static string ReadXmlEnumAttribute(this Enum value)
    {
        if (value == null) throw new ArgumentNullException("value");
        var attribs = (XmlEnumAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (XmlEnumAttribute), true);
        return attribs.Length > 0 ? attribs[0].Name : value.ToString();
    }

    public static T ParseXmlEnumAttribute<T>(this string str)
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            var attribs = (XmlEnumAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(XmlEnumAttribute), true);
            if(attribs.Length > 0 && attribs[0].Name.Equals(str)) return item;
        }
        return (T)Enum.Parse(typeof(T), str, true);
    }
}

public enum MyEnum
{
    [XmlEnum("First Value")]
    One,
    [XmlEnum("Second Value")]
    Two,
    Three
}

 static void Main()
 {
    // Parsing from XmlEnum attribute
    var str = "Second Value";
    var me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    // Parsing without XmlEnum
    str = "Three";
    me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    me = MyEnum.One;
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
}

나는 샘플을 dimarzionist로 수정했다. 이 버전은 Enums에서만 작동하며 구조체를 통과시키지 않습니다.

public static T ParseEnum<T>(string enumString)
    where T : struct // enum 
    {
    if (String.IsNullOrEmpty(enumString) || !typeof(T).IsEnum)
       throw new Exception("Type given must be an Enum");
    try
    {

       return (T)Enum.Parse(typeof(T), enumString, true);
    }
    catch (Exception ex)
    {
       return default(T);
    }
}

나는 항상 이것을 좋아했다 (당신은 적절하게 수정할 수 있었다) :

public static IEnumerable<TEnum> GetEnumValues()
{
  Type enumType = typeof(TEnum);

  if(!enumType.IsEnum)
    throw new ArgumentException("Type argument must be Enum type");

  Array enumValues = Enum.GetValues(enumType);
  return enumValues.Cast<TEnum>();
}

또한 Enum 제약 조건을 사용하는 C # 7.3의 릴리스는 추가 검사 및 작업을 수행하지 않고도 즉시 사용할 수 있도록 지원됩니다.

따라서 앞으로 프로젝트의 언어 버전을 C # 7.3으로 변경하면 다음 코드가 완벽하게 작동합니다.

    private static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
    {
        // Your code goes here...
    }

언어 버전을 C # 7.3으로 변경하는 방법을 모르는 경우 다음 스크린 샷을 참조하십시오.

편집 1 - 필요한 Visual Studio 버전 및 ReSharper 고려

Visual Studio에서 새 구문을 인식하려면 최소한 버전 15.7이 필요합니다. Microsoft의 릴리스 정보에서도 언급 된 것을 찾을 수 있습니다 ( Visual Studio 2017 15.7 릴리스 노트 참조) . 이 유효한 질문을 지적 해 주신 @MohamedElshawaf에게 감사드립니다.

Pls는 또한 필자의 경우 ReSharper 2018.1을 쓰는 시점에서이 EDIT가 아직 C # 7.3을 지원하지 않는다고 언급합니다. ReSharper를 활성화하면 Type 매개 변수 제약 조건으로 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object'를 사용할 수 없다는 오류로 Enum 제약 조건이 강조 표시됩니다. ReSharper는 메서드의 매개 변수 T의 'Enum'제약 조건제거 하는 빠른 수정 방법을 제안합니다.

그러나 도구 -> 옵션 -> ReSharper Ultimate -> General 에서 ReSharper를 일시적으로 해제 한 경우 VS 15.7 이상 및 C # 7.3 이상을 사용하면 구문이 완벽하게 올바르게 표시됩니다.


이것은 그것에 나의 포획이다. 답변과 MSDN의 결합

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable
{
    if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)
        throw new ArgumentException("TEnum must be an Enum type");

    try
    {
        var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);
        return enumValue;
    }
    catch (Exception)
    {
        throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));
    }
}

MSDN 소스


일리노이를 사용하는 Christopher Currens의 솔루션을 좋아했지만 MSIL을 자신의 빌드 프로세스에 포함시키는 까다로운 비즈니스를 처리하고 싶지 않은 사람들을 위해 C #에서 비슷한 기능을 썼습니다.

열거 형은 특수 유형이므로 where T : Enum 형과 같은 일반적인 제한을 사용할 수는 없지만 유의하십시오. 따라서 주어진 generic 형식이 실제로 enum인지 확인해야합니다.

내 기능은 다음과 같습니다.

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Method GetEnumFromString can be used with enums only");

    if (!string.IsNullOrEmpty(strValue))
    {
        IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            T temp = (T)enumerator.Current;
            if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))
                return temp;
        }
    }

    return defaultValue;
}

코드를 약간 개선하려고했습니다.

public T LoadEnum<T>(string value, T defaultValue = default(T)) where T : struct, IComparable, IFormattable, IConvertible
{
    if (Enum.IsDefined(typeof(T), value))
    {
        return (T)Enum.Parse(typeof(T), value, true);
    }
    return defaultValue;
}

흥미롭게도, 이것은 분명히 다른 langauges (Managed C ++, 일리노이 직접)에서 가능합니다.

인용하기 :

... 두 제약 조건은 실제로 유효한 IL을 생성하며 다른 언어로 작성된 경우 C #에서 소비 할 수도 있습니다 (관리되는 C ++ 또는 IL에서 이러한 제약 조건을 선언 할 수 있음).

누가 알아


희망이 도움이 :

public static TValue ParseEnum<TValue>(string value, TValue defaultValue)
                  where TValue : struct // enum 
{
      try
      {
            if (String.IsNullOrEmpty(value))
                  return defaultValue;
            return (TValue)Enum.Parse(typeof (TValue), value);
      }
      catch(Exception ex)
      {
            return defaultValue;
      }
}

편집하다

질문은 이제 Julien Lebosquain 에 의해 훌륭하게 답변되었습니다. 또한 TryParseParseOrDefault 를 추가하는 동안 ignoreCase , defaultValue 및 선택적 인수를 사용하여 답변을 확장하고 ParseOrDefault .

public abstract class ConstrainedEnumParser<TClass> where TClass : class
// value type constraint S ("TEnum") depends on reference type T ("TClass") [and on struct]
{
    // internal constructor, to prevent this class from being inherited outside this code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtils
    public static TEnum Parse<TEnum>(string value, bool ignoreCase = false) where TEnum : struct, TClass
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);
    }
    public static bool TryParse<TEnum>(string value, out TEnum result, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        var didParse = Enum.TryParse(value, ignoreCase, out result);
        if (didParse == false)
        {
            result = defaultValue;
        }
        return didParse;
    }
    public static TEnum ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum result;
        if (Enum.TryParse(value, ignoreCase, out result)) { return result; }
        return defaultValue;
    }
}

public class EnumUtils: ConstrainedEnumParser<System.Enum>
// reference type constraint to any <System.Enum>
{
    // call to parse will then contain constraint to specific <System.Enum>-class
}

사용 예 :

WeekDay parsedDayOrArgumentException = EnumUtils.Parse<WeekDay>("monday", ignoreCase:true);
WeekDay parsedDayOrDefault;
bool didParse = EnumUtils.TryParse<WeekDay>("clubs", out parsedDayOrDefault, ignoreCase:true);
parsedDayOrDefault = EnumUtils.ParseOrDefault<WeekDay>("friday", ignoreCase:true, defaultValue:WeekDay.Sunday);

늙은

의견과 '새로운'개발을 사용하여 share 에 share 나의 이전 개선 사항 :

  • 사용자의 TEnum 를 위해 TEnum 사용
  • 추가 제약 조건 검사를위한 인터페이스 제약 조건 추가
  • TryParse 가 기존 매개 변수 (VS2010 / .Net 4에 도입 된)로 ignoreCase 를 처리하게합니다.
  • 선택적으로 일반적인 default 값을 사용합니다 (VS2005 / .Net 2에서 소개 됨)
  • 선택적 인수 (VS2010 / .Net 4에서 소개)를 defaultValueignoreCase 대한 기본값으로 사용하십시오.

를 야기하는:

public static class EnumUtils
{
    public static TEnum ParseEnum<TEnum>(this string value,
                                         bool ignoreCase = true,
                                         TEnum defaultValue = default(TEnum))
        where TEnum : struct,  IComparable, IFormattable, IConvertible
    {
        if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum lResult;
        if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }
        return defaultValue;
    }
}

자바에서는 다음을 사용합니다.

    SomeClass<T extends enum> {
}

아주 간단합니다.





generic-constraints