Enum ile T sınırlayan Genel yöntem oluşturma


1188

Şu Enum.Parsekavramları genişletmek için bir işlev inşa ediyorum:

  • Bir Enum değerinin bulunmaması durumunda varsayılan bir değerin ayrıştırılmasına izin verir
  • Büyük / küçük harfe duyarlı değil mi

Bu yüzden aşağıdakileri yazdım:

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

Bir hata kısıtlaması özel sınıf olamaz alıyorum System.Enum.

Yeterince adil, ama bir Genel Enum izin vermek için bir geçici çözüm var, ya da ben Parseişlev taklit ve bir tür öznitelik olarak kod geçmek çirkin boks gereksinimi zorlamak zorunda kalacak mıyım .

EDIT Aşağıdaki tüm öneriler büyük beğeni topluyor, teşekkürler.

Yerleşim var (büyük / küçük harf duyarsızlığını korumak için döngüden ayrıldım - XML ​​ayrıştırılırken bunu kullanıyorum)

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

EDIT: (16 Şubat 2015) Julien Lebosquain kısa bir süre önce MSIL veya F # 'de derleyici tarafından zorlanan tip-güvenli bir genel çözüm yayınladı , ki bu da bir göz atmaya ve bir oyuna değer. Çözüm sayfayı daha da yukarı çekerse bu düzenlemeyi kaldıracağım.


10
Belki kullanmalıdır ToUpperInvariant () yerine ToLower (...)
Max Galkin

31
@Shimmy: Uzantı yöntemine bir değer türü iletir iletmez, bunun bir kopyası üzerinde çalışıyorsunuz, böylece durumunu değiştiremezsiniz.
Garo Yeriazarian

4
Eski bir iş parçacığı olduğunu bilin, bir şeyleri değiştirip değiştirmediklerini bilmiyorum, ancak uzantı yöntemleri değer türleri için iyi çalışıyor, her zaman mantıklı gelmeyeceklerinden emin olabilirim, ancak "genel statik TimeSpan Saniye (this int x) { "Wait.For (5.Seconds ()) ..." sözdizimini etkinleştirmek için TimeSpan.FromSeconds (x);} "değerini döndürün
Jens

6
Bunun sorunun bir parçası olmadığını, ancak StringComparison.InvariantCultureIgnoreCase
Firestrand

Yanıtlar:


1006

Yana EnumTipi uygular IConvertiblearayüz, daha iyi bir uygulama aşağıdaki gibi bir şey olmalı:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible
{
   if (!typeof(T).IsEnum) 
   {
      throw new ArgumentException("T must be an enumerated type");
   }

   //...
}

Bu yine de değer türlerinin uygulanmasına izin verecektir IConvertible. Şansı nadirdir.


2
Jenerikler, .NET 2.0'dan beri mevcuttur. Dolayısıyla vb 2005 de mevcuttur.
Vivek

46
Peki, daha da kısıtlı hale getirin, eğer bu yola inmeyi seçerseniz ... "sınıf TestClass <T> kullanın burada T: struct, IComparable, IFormattable, IConvertible"
Ricardo Nolde

106
Diğer bir öneri de genel türü TEnum tanımlayıcısı ile tanımlamaktır. Böylece: genel TEnum GetEnumFromString <TEnum> (dize değeri) burada TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa

11
Diğer arayüzleri dahil ederek fazla bir kazanç sağlamazsınız çünkü yerleşik değer türlerinin neredeyse tamamı bu arayüzlerin tümünü uygular. Bu özellikle, bu uzantı yöntemlerinin tüm nesnelerinize bulaşan bir virüs gibi olması dışında, numaralandırmalarda son derece kullanışlı olan genel bir uzantı yöntemindeki kısıtlamalar için geçerlidir. IConvertable en azından biraz daraltıyor.
russbishop

2
@SamIam: Gönderdiğinizde, bu konu 6 buçuk yaşındaydı ve haklıydınız, cevapların hiçbirinde derleme zamanı kontrolü yok. Sonra sadece 3 gün sonra, 6 yıl sonra dileğini aldın - Julien Lebosquain'in posta yolunu aşağıya bakın.
David I. McIntosh

662

Bu özellik sonunda C # 7.3'te desteklenmektedir!

Aşağıdaki kod parçası ( dotnet örneklerinden ) nasıl olduğunu gösterir:

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 # projenizde dil sürümünüzü 7.3 sürümüne ayarladığınızdan emin olun.


Aşağıdaki orijinal cevap:

Oyuna geç kaldım, ama nasıl yapılabileceğini görmek için bir meydan okuma olarak aldım. C # (veya VB.NET'te mümkün değildir, ancak F # için aşağı kaydırın), ancak MSIL'de mümkündür . Bu küçük şeyi yazdım.

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

Hangi bir işlev oluşturur ediyorum geçerli C # olsaydı, şu şekilde görünür:

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

Sonra aşağıdaki C # kodu ile:

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
}

Ne yazık ki, bu, kodunuzun bu bölümünün C # yerine MSIL'de yazılması anlamına gelir; tek ek avantajı, bu yöntemi kısıtlayabilmenizdir System.Enum. Aynı zamanda bir serseri, çünkü ayrı bir montajda derlenir. Ancak, bu şekilde dağıtmanız gerektiği anlamına gelmez.

Çizgiyi kaldırarak .assembly MyThing{}ve ilasmı aşağıdaki gibi çağırarak:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

montaj yerine bir netmodül elde edersiniz.

Ne yazık ki, VS2010 (ve daha önceki sürümlerde) netmodule referanslarının eklenmesini desteklemez, yani hata ayıklama sırasında 2 ayrı derlemede bırakmanız gerekir. Bunları derlemenizin bir parçası olarak eklemenin tek yolu, /addmodule:{files}komut satırı bağımsız değişkenini kullanarak csc.exe dosyasını kendiniz çalıştırmak olacaktır . Bir MSBuild komut dosyasında çok acı verici olmaz . Tabii ki, cesur veya aptalsanız, csc'yi her seferinde manuel olarak çalıştırabilirsiniz. Ve çoklu montajların buna erişmesi gerektiğinden kesinlikle daha karmaşık hale gelir.

Yani, .Net'te yapılabilir. Ekstra çabaya değer mi? Şey, sanırım buna karar vermene izin vereceğim.


F # Alternatif olarak çözüm

Ekstra Kredi: enumMSIL: F # dışında en az bir .NET dilinde genel bir kısıtlamanın mümkün olduğu ortaya çıkıyor .

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

Tam Visual Studio IDE desteği ile iyi bilinen bir dil olduğu için bu dilin bakımı daha kolaydır, ancak yine de bunun için çözümünüzde ayrı bir projeye ihtiyacınız vardır. Bununla birlikte, doğal olarak ürettiği ölçüde farklı OL (kod olan çok farklı) ve dayanırFSharp.Core tıpkı diğer dış kitaplığı gibi Dağıtımınız bir parçası haline gerekiyor, kütüphane,.

Bunu (temel olarak MSIL çözümüyle aynı şekilde) nasıl kullanabileceğiniz ve başka türlü eşanlamlı yapılarda doğru şekilde başarısız olduğunu göstermek için:

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

67
Evet, çok sert. Ben IL içinde kod, birisi için son derece saygı ve çoğumuz hala uygulamaları, iş kuralları, UI en, bileşen kütüphaneleri, vb altında düşük seviye olarak bkz seviyede - özellikleri yüksek lisan düzeyinde desteklenir biliyorum .
TonyG

13
Gerçekten bilmek istediğim, CIL ekibinin neden buna henüz izin vermediği, çünkü zaten MSIL tarafından destekleniyor.
MgSam

25
@MgSam - Eric Lippert'den :There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.
Christopher Currens

5
@LordofScripts: Ben nedeni kısıtlayan bir sınıf beri olduğunu düşünüyorum Tetmek System.Enumile her şeyi yapmak mümkün olmaz Tbekliyoruz olabileceğini insanlar, C # yazarları onlar da tümüyle yasaklayabilir düşündüm. Kararın talihsiz olduğunu düşünüyorum, çünkü C # System.Enumkısıtlamaların herhangi bir özel işlenmesini görmezden HasAnyFlags<T>(this T it, T other)geldiği için, büyüklük sıralarından daha hızlı olan Enum.HasFlag(Enum)ve argümanlarını hangi tipte kontrol ettiği bir uzatma yöntemi yazmak mümkün olurdu .
supercat

9
Buraya kadar gelmediğim bir projem olduğunu hiç sanmıyorum. C # 6% 110 sözdizimsel şeker ve BU girmedi? Saçmalamayı kesin.
Michael Blackburn

214

C # ≥ 7.3

C # 7.3 ile başlayarak (Visual Studio 2017 ≥ v15.7 ile kullanılabilir), bu kod artık tamamen geçerlidir:

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

C # ≤ 7.2

Kısıt devralmayı kötüye kullanarak gerçek bir derleyici tarafından zorlanmış numaralandırma kısıtlamanız olabilir. Aşağıdaki kod a hem belirtir classve bir structaynı anda kısıtlamaları:

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

Kullanımı:

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

Not: Bu özellikle C # 5.0 dil spesifikasyonunda belirtilmiştir:

S tipi parametresi T tip parametresine bağlıysa: [...] S'nin değer tipi kısıtlamasına sahip olması ve T'nin referans tipi kısıtlamasına sahip olması geçerlidir. Bu etkili bir şekilde T'yi System.Object, System.ValueType, System.Enum ve herhangi bir arabirim türüyle sınırlar.


7
@ DavidI.McIntosh EnumClassUtils<System.Enum>, T'yi System.Enumtüretilmiş türlerle sınırlamak için yeterlidir . structüzerinde Parsedaha sonra gerçek enum türüne daha da kısıtlar. Bir Enumnoktada kısıtlamanız gerekir . Bunun için sınıfınızın iç içe yerleştirilmesi gerekir. Bkz. Gist.github.com/MrJul/7da12f5f2d6c69f03d79
Julien Lebosquain

7
Sadece açık olmak gerekirse, benim yorum "hoş değil" çözümünüzün bir yorum değildi - gerçekten güzel bir kesmek. MS'in bizi böyle kıvrık bir hack kullanmaya zorlaması “hoş değil”.
David I. McIntosh

2
Bunu genişletme yöntemleri için de kullanılabilir hale getirmenin bir yolu var mı?
Mord Zuber

3
where TClass : classBurada kısıtlama ne kazanıyor?
tsemer

2
@Trinkyoenum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }
M.Stramm

30

Düzenle

Soru şu anda Julien Lebosquain tarafından mükemmel bir şekilde cevaplandı . Ben de onun ile cevap uzatmak istiyorum ignoreCase, defaultValueeklerken, ve opsiyonel argümanları TryParseve 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
}

Kullanım örnekleri:

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

Eski

Yorumları ve 'yeni' gelişmeleri kullanarak Vivek'in cevabındaki eski geliştirmelerim:

  • kullanım TEnumkullanıcılar için netlik için
  • ek kısıtlama kontrolü için daha fazla arayüz kısıtlaması ekleyin
  • let TryParsekoluignoreCase , mevcut parametresiyle (VS2010 tanıtılan / Net 4)
  • isteğe bağlı olarak genel değeri kullanındefault (VS2005 / .Net 2'de kullanılır)
  • kullanımı isteğe bağlı argümanlar için, varsayılan değerler ile (VS2010 / Net 4'ten) defaultValueveignoreCase

sonuçlanan:

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

18

Sınıf için T türünün bir enum olup olmadığını kontrol edecek ve değilse bir istisna atacak sınıf için statik bir kurucu tanımlayabilirsiniz. Bu, Jeffery Richter tarafından C # üzerinden CLR kitabında belirtilen yöntemdir.

internal sealed class GenericTypeThatRequiresAnEnum<T> {
    static GenericTypeThatRequiresAnEnum() {
        if (!typeof(T).IsEnum) {
        throw new ArgumentException("T must be an enumerated type");
        }
    }
}

Daha sonra ayrıştırma yönteminde, dizeden numaraya dönüştürmek için Enum.Parse (typeof (T), input, true) kullanabilirsiniz. Son doğru parametre, giriş durumunu görmezden gelmek içindir.


1
Bu, genel sınıflar için iyi bir seçenektir - ancak elbette, genel yöntemlere yardımcı olmaz.
McGarnagle

Ayrıca, bu derleme zamanında da uygulanmaz, yalnızca Enum Tyapıcı çalıştırıldığında bir non sağladığınızı bilirsiniz . Rağmen bu bir örnek yapıcı beklemek çok daha güzel.
jrh

15

Ayrıca, Enum kısıtlamalarını kullanarak C # 7.3'ün piyasaya sürülmesinden sonra ek kontrol ve malzeme yapmak zorunda kalmadan kutudan çıkarıldığı da desteklenmelidir.

Bu nedenle, projenizin dil sürümünü C # 7.3 olarak değiştirdiğinizde, aşağıdaki kod mükemmel şekilde çalışacaktır:

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

Dil sürümünü C # 7.3 olarak nasıl değiştireceğinizi bilmiyorsanız, aşağıdaki ekran görüntüsüne bakın: resim açıklamasını buraya girin

DÜZENLEME 1 - Gerekli Visual Studio Sürümü ve ReSharper'ı dikkate alma

Visual Studio'nun yeni sözdizimini tanıması için en az 15.7 sürümüne ihtiyacınız vardır. Microsoft'un sürüm notlarında da belirtilenleri bulabilirsiniz, bkz. Visual Studio 2017 15.7 Sürüm Notları . Bu geçerli soruyu işaret ettiğiniz için @MohamedElshawaf'a teşekkür ederiz.

Pls ayrıca benim durumumda ReSharper 2018.1'in bu EDIT'i yazarken henüz C # 7.3'ü desteklemediğini unutmayın. ReSharper'ı etkinleştirdikten sonra, Enum kısıtlamasını bana 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' türünü parametre kısıtlaması olarak kullanamadığımı söyleyen bir hata olarak vurgular . ReSharper , yöntemin T parametre parametresi 'Enum' kısıtlamasını kaldırmak için hızlı bir düzeltme önermektedir

Bununla birlikte, Araçlar -> Seçenekler -> ReSharper Ultimate -> Genel altında ReSharper'ı geçici olarak kapatırsanız, VS 15.7 veya üstü ve C # 7.3 veya üstü kullandığınız sürece sözdiziminin mükemmel olduğunu görürsünüz.


1
Hangi VS sürümünü kullanıyorsunuz?
mshwf

1
@MohamedElshawaf C # 7.3 için destek içeren 15.7 sürümü olduğuna inanıyorum
Patrick Roberts

1
Tip parametresi olarak kendini where T : struct, Enumgeçmekten kaçınmak, yazmak daha iyi olduğunu düşünüyorum System.Enum.
Mariusz Pawelski

@MariuszPawelski gibi yazıyorum struct, Enum. Benim mantığı cevapta açıklandığı ve yorumlar edilir burada .
Stephen Kennedy

ReSharper bilgisi bana gerçekten yardımcı oldu. En son önizleme sürümünün bu özelliği desteklediğini unutmayın.
DalSoft

11

Örneği dimarzionist tarafından değiştirdim. Bu sürüm yalnızca Numaralandırmalarla çalışır ve yapıların geçmesine izin vermez.

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

13
Başarısızlık varsayılan değeri döndürmek olmaz; İstisnanın yayılmasına izin verdim (tıpkı Enum.Parse ile olduğu gibi). Bunun yerine, bir bool döndürerek TryParse kullanın ve bir çıkış parametresi kullanarak sonucu döndürün.
Mark Simpson

1
OP büyük / küçük harfe duyarlı olmamasını istiyor, bu değil.
Konrad Morawski

9

Kodu biraz geliştirmeye çalıştım:

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

1
Bu, kabul edilen cevaptan daha iyidir, çünkü defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)hangi tür numaralandırmayı bilmiyor olsanız bile, sadece nesnenin bir numaralandırma olduğunu çağırmanıza izin verir .
styfle

1
Bununla birlikte IsDefined, önceden kontrol , vaka duyarsızlığını bozacaktır. Aksine Parse, IsDefinedhiçbir ignoreCaseargümanı yoktur ve MSDN bunun sadece tam durumla eşleştiğini söylüyor .
Nyerguds

5

Ben enum değeri ile ilişkili metin ile enum kullanmak için gereken özel bir gereksinim var. Örneğin enum'u hata türünü belirtmek için kullandığımda hata ayrıntılarını açıklamak gerekiyor.

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

4

Umarım bu yardımcı olur:

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

1
Eğer durum duyarsız gerekiyorsa, basitçe yerine return (TValue)Enum.Parse(typeof (TValue), value);görereturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos

3

İlginçtir, görünüşe göre bu diğer dillerde mümkündür (doğrudan Managed C ++, IL).

Alıntılamak:

... Her iki kısıtlama da aslında geçerli IL üretir ve başka bir dilde yazılmışsa C # tarafından da kullanılabilir (bu kısıtlamaları yönetilen C ++ veya IL'de bildirebilirsiniz).

Kim bilir


2
C ++ için Yönetilen Uzantıların jenerikler için HERHANGİ bir desteği yok, bence C ++ / CLI demek istediniz.
Ben Voigt

3

Bu benim almam. Cevaplar ve MSDN'den birleştirilmiş

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 Kaynağı


2
Bu gerçekten mantıklı değil. Eğer TEnumgerçekte bir Enum türüdür ama textboş bir dizedir o zaman olsun bir ArgumentExceptiono olsa bile "TEnum bir Enum türü olmalıdır" diyerek.
Nick

3

Mevcut cevaplar C # <= 7.2 itibarıyla geçerlidir. Ancak, aşağıdakilere izin vermek için bir C # dili özellik isteği (bir corefx özellik isteğine bağlı) vardır;

public class MyGeneric<TEnum> where TEnum : System.Enum
{ }

Yazım sırasında, dil geliştirme toplantılarında bu özellik "Tartışmada" dır.

DÜZENLE

Gereğince Nawfal 'ın bilgi, bu C # tanıtılan ediliyor 7.3 .


1
İlginç bir tartışma var, teşekkürler. Henüz taş henüz bir şey
koymadı

1
@johnc, çok doğru ama değer bir not ve onu olduğu bir çok sorulan özellik. Adil oranlar geliyor.
DiskJunky


1

Bunu her zaman sevdim (uygun şekilde değiştirebilirsiniz):

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

1

Christopher Currens'in IL kullanarak çözümünü sevdim ama MSIL'i oluşturma sürecine dahil etmek zor işlerle uğraşmak istemeyenler için C # 'da benzer bir fonksiyon yazdım.

Ancak, where T : EnumEnum özel bir tür olduğu için genel kısıtlama kullanamayacağınızı unutmayın . Bu nedenle, verilen jenerik tipin gerçekten enum olup olmadığını kontrol etmeliyim.

Benim fonksiyonum:

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

1

Vivek'in çözümünü tekrar kullanabileceğiniz bir yardımcı program sınıfına ekledim. Lütfen yine de türünüzde "T: struct, IConvertible" türünde kısıtlamalar tanımlamanız gerektiğini unutmayın.

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

1

Bir yöntem oluşturdum to get integer value from enum yöntem uygulama bakmak bakmak

public static int ToInt<T>(this T soure) where T : IConvertible//enum
{
    if (typeof(T).IsEnum)
    {
        return (int) (IConvertible)soure;// the tricky part
    }
    //else
    //    throw new ArgumentException("T must be an enumerated type");
    return soure.ToInt32(CultureInfo.CurrentCulture);
}

bu kullanım

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way

Muhtemelen işe yarasa da, soruyla neredeyse hiç ilgisi yoktur.
quetzalcoatl

1

Daha önce verilen diğer cevaplarda belirtildiği gibi; bu kaynak kodunda ifade edilemezken, aslında IL Düzeyinde yapılabilir. @Cristopher Currens cevabı , IL'nin bunu nasıl yaptığını gösterir.

İle Fody s Eklenti ExtraConstraints.Fody bunu başarmak için yap-kalıp ile tam çok basit bir yolu, var. Sadece nuget paketlerini ( Fody, ExtraConstraints.Fody) projenize ekleyin ve kısıtlamaları aşağıdaki gibi ekleyin (ExtraConstraints Benioku alıntısı):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}

public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

ve Fody kısıtlamanın mevcut olması için gerekli IL'yi ekleyecektir. Ayrıca, delegeleri kısıtlamanın ek özelliğine de dikkat edin:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> ()
{...}

Enums ile ilgili olarak, son derece ilginç olanları da not etmek isteyebilirsiniz. Enums.NET'i .


1

Bu benim uygulamam. Temel olarak, herhangi bir niteliği ayarlayabilirsiniz ve çalışır.

public static class EnumExtensions
    {
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            FieldInfo fi = type.GetField(@enum.ToString());
            DescriptionAttribute[] attrs =
                fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
            if (attrs.Length > 0)
            {
                return attrs[0].Description;
            }
            return null;
        }
    }

0

Daha sonra doğrudan döküm kullanmakta sorun yoksa System.Enum, gerekli olduğunda temel sınıfı yönteminizde kullanabilirsiniz . Sadece tip parametrelerini dikkatlice değiştirmeniz gerekir. Yani yöntem uygulaması şöyle olacaktır:

public static class EnumUtils
{
    public static Enum GetEnumFromString(string value, Enum defaultValue)
    {
        if (string.IsNullOrEmpty(value)) return defaultValue;
        foreach (Enum item in Enum.GetValues(defaultValue.GetType()))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

Sonra şöyle kullanabilirsiniz:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);

kullanımı Enum.ToObject()daha esnek bir sonuç verecektir. Buna ek olarak, arama gereksinimini ortadan kaldıracak büyük / küçük harf duyarlılığı olmadan dize karşılaştırmaları yapabilirsinizToLower()
DiskJunky

-6

Tam olarak söylemek gerekirse, aşağıdakiler bir Java çözümüdür. Aynı C # de yapılabilir eminim. Türü kodun herhangi bir yerinde belirtmek zorunda kalmaz - bunun yerine, ayrıştırmaya çalıştığınız dizelerde belirtirsiniz.

Sorun, Dize'nin hangi numaralandırmayla eşleşebileceğini bilmenin bir yolu olmamasıdır - bu yüzden cevap bu sorunu çözmektir.

Yalnızca dize değerini kabul etmek yerine, "enumeration.value" biçiminde hem numaralandırmaya hem de değere sahip bir Dize kabul edin. Çalışma kodu aşağıda - Java 1.8 veya üstünü gerektirir. Bu, XML'i yalnızca color = "red" yerine color = "Color.red" gibi bir şey gördüğünüz gibi daha hassas hale getirir.

AcceptEnumeratedValue () yöntemini, numaralandırma adı nokta değeri adını içeren bir dize ile çağırırsınız.

Yöntem, resmi numaralandırılmış değeri döndürür.

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;


public class EnumFromString {

    enum NumberEnum {One, Two, Three};
    enum LetterEnum {A, B, C};


    Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();

    public static void main(String[] args) {
        EnumFromString efs = new EnumFromString();

        System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());
        System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());

    }

    public EnumFromString() {
        enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});
        enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});
    }

    public Enum acceptEnumeratedValue(String enumDotValue) {

        int pos = enumDotValue.indexOf(".");

        String enumName = enumDotValue.substring(0, pos);
        String value = enumDotValue.substring(pos + 1);

        Enum enumeratedValue = enumsByName.get(enumName).apply(value);

        return enumeratedValue;
    }


}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.