我有以下列举:
public enum AuthenticationMethod
{
FORMS = 1,
WINDOWSAUTHENTICATION = 2,
SINGLESIGNON = 3
}
然而问题是,当我请求AuthenticationMethod时,我需要“FORMS”这个词。表单而不是id
对于这个问题,我找到了以下解决方案(链接):
首先,我需要创建一个自定义属性“StringValue”:
public class StringValue : System.Attribute
{
private readonly string _value;
public StringValue(string value)
{
_value = value;
}
public string Value
{
get { return _value; }
}
}
然后我可以将这个属性添加到我的枚举器中:
public enum AuthenticationMethod
{
[StringValue("FORMS")]
FORMS = 1,
[StringValue("WINDOWS")]
WINDOWSAUTHENTICATION = 2,
[StringValue("SSO")]
SINGLESIGNON = 3
}
当然,我需要一些东西来检索StringValue:
public static class StringEnum
{
public static string GetStringValue(Enum value)
{
string output = null;
Type type = value.GetType();
//Check first in our cached results...
//Look for our 'StringValueAttribute'
//in the field's custom attributes
FieldInfo fi = type.GetField(value.ToString());
StringValue[] attrs =
fi.GetCustomAttributes(typeof(StringValue),
false) as StringValue[];
if (attrs.Length > 0)
{
output = attrs[0].Value;
}
return output;
}
}
很好,现在我已经有了工具来获取枚举器的字符串值。
然后我可以这样使用它:
string valueOfAuthenticationMethod = StringEnum.GetStringValue(AuthenticationMethod.FORMS);
好的,现在所有这些工作就像一个魅力,但我发现它有很多工作。我想知道有没有更好的解决办法。
我还尝试了一些字典和静态属性,但这也不是更好。
更新:访问这个页面,8年后,在很长一段时间没有接触c#之后,看起来我的答案不再是最好的解决方案。我非常喜欢与属性-函数绑定的转换器解决方案。
如果你正在阅读这篇文章,请确保你也查看了其他答案。(提示:它们在这个上面)
和你们大多数人一样,我非常喜欢Jakub Šturc所选的答案,但我也非常讨厌复制粘贴代码,并尽可能少地这样做。
因此,我决定使用一个EnumBase类,从其中继承/内置大部分功能,从而使我能够专注于内容而不是行为。
这种方法的主要问题是基于这样一个事实,即尽管Enum值是类型安全的实例,但交互是与Enum Class类型的静态实现进行的。
所以在泛型魔法的帮助下,我想我终于得到了正确的组合。
希望有人能像我一样觉得这个有用。
我将从Jakub的例子开始,但是使用继承和泛型:
public sealed class AuthenticationMethod : EnumBase<AuthenticationMethod, int>
{
public static readonly AuthenticationMethod FORMS =
new AuthenticationMethod(1, "FORMS");
public static readonly AuthenticationMethod WINDOWSAUTHENTICATION =
new AuthenticationMethod(2, "WINDOWS");
public static readonly AuthenticationMethod SINGLESIGNON =
new AuthenticationMethod(3, "SSN");
private AuthenticationMethod(int Value, String Name)
: base( Value, Name ) { }
public new static IEnumerable<AuthenticationMethod> All
{ get { return EnumBase<AuthenticationMethod, int>.All; } }
public static explicit operator AuthenticationMethod(string str)
{ return Parse(str); }
}
这里是基类:
using System;
using System.Collections.Generic;
using System.Linq; // for the .AsEnumerable() method call
// E is the derived type-safe-enum class
// - this allows all static members to be truly unique to the specific
// derived class
public class EnumBase<E, T> where E: EnumBase<E, T>
{
#region Instance code
public T Value { get; private set; }
public string Name { get; private set; }
protected EnumBase(T EnumValue, string Name)
{
Value = EnumValue;
this.Name = Name;
mapping.Add(Name, this);
}
public override string ToString() { return Name; }
#endregion
#region Static tools
static private readonly Dictionary<string, EnumBase<E, T>> mapping;
static EnumBase() { mapping = new Dictionary<string, EnumBase<E, T>>(); }
protected static E Parse(string name)
{
EnumBase<E, T> result;
if (mapping.TryGetValue(name, out result))
{
return (E)result;
}
throw new InvalidCastException();
}
// This is protected to force the child class to expose it's own static
// method.
// By recreating this static method at the derived class, static
// initialization will be explicit, promising the mapping dictionary
// will never be empty when this method is called.
protected static IEnumerable<E> All
{ get { return mapping.Values.AsEnumerable().Cast<E>(); } }
#endregion
}
更新:访问这个页面,8年后,在很长一段时间没有接触c#之后,看起来我的答案不再是最好的解决方案。我非常喜欢与属性-函数绑定的转换器解决方案。
如果你正在阅读这篇文章,请确保你也查看了其他答案。(提示:它们在这个上面)
和你们大多数人一样,我非常喜欢Jakub Šturc所选的答案,但我也非常讨厌复制粘贴代码,并尽可能少地这样做。
因此,我决定使用一个EnumBase类,从其中继承/内置大部分功能,从而使我能够专注于内容而不是行为。
这种方法的主要问题是基于这样一个事实,即尽管Enum值是类型安全的实例,但交互是与Enum Class类型的静态实现进行的。
所以在泛型魔法的帮助下,我想我终于得到了正确的组合。
希望有人能像我一样觉得这个有用。
我将从Jakub的例子开始,但是使用继承和泛型:
public sealed class AuthenticationMethod : EnumBase<AuthenticationMethod, int>
{
public static readonly AuthenticationMethod FORMS =
new AuthenticationMethod(1, "FORMS");
public static readonly AuthenticationMethod WINDOWSAUTHENTICATION =
new AuthenticationMethod(2, "WINDOWS");
public static readonly AuthenticationMethod SINGLESIGNON =
new AuthenticationMethod(3, "SSN");
private AuthenticationMethod(int Value, String Name)
: base( Value, Name ) { }
public new static IEnumerable<AuthenticationMethod> All
{ get { return EnumBase<AuthenticationMethod, int>.All; } }
public static explicit operator AuthenticationMethod(string str)
{ return Parse(str); }
}
这里是基类:
using System;
using System.Collections.Generic;
using System.Linq; // for the .AsEnumerable() method call
// E is the derived type-safe-enum class
// - this allows all static members to be truly unique to the specific
// derived class
public class EnumBase<E, T> where E: EnumBase<E, T>
{
#region Instance code
public T Value { get; private set; }
public string Name { get; private set; }
protected EnumBase(T EnumValue, string Name)
{
Value = EnumValue;
this.Name = Name;
mapping.Add(Name, this);
}
public override string ToString() { return Name; }
#endregion
#region Static tools
static private readonly Dictionary<string, EnumBase<E, T>> mapping;
static EnumBase() { mapping = new Dictionary<string, EnumBase<E, T>>(); }
protected static E Parse(string name)
{
EnumBase<E, T> result;
if (mapping.TryGetValue(name, out result))
{
return (E)result;
}
throw new InvalidCastException();
}
// This is protected to force the child class to expose it's own static
// method.
// By recreating this static method at the derived class, static
// initialization will be explicit, promising the mapping dictionary
// will never be empty when this method is called.
protected static IEnumerable<E> All
{ get { return mapping.Values.AsEnumerable().Cast<E>(); } }
#endregion
}
我的变体
public struct Colors
{
private String current;
private static string red = "#ff0000";
private static string green = "#00ff00";
private static string blue = "#0000ff";
private static IList<String> possibleColors;
public static Colors Red { get { return (Colors) red; } }
public static Colors Green { get { return (Colors) green; } }
public static Colors Blue { get { return (Colors) blue; } }
static Colors()
{
possibleColors = new List<string>() {red, green, blue};
}
public static explicit operator String(Colors value)
{
return value.current;
}
public static explicit operator Colors(String value)
{
if (!possibleColors.Contains(value))
{
throw new InvalidCastException();
}
Colors color = new Colors();
color.current = value;
return color;
}
public static bool operator ==(Colors left, Colors right)
{
return left.current == right.current;
}
public static bool operator !=(Colors left, Colors right)
{
return left.current != right.current;
}
public bool Equals(Colors other)
{
return Equals(other.current, current);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (obj.GetType() != typeof(Colors)) return false;
return Equals((Colors)obj);
}
public override int GetHashCode()
{
return (current != null ? current.GetHashCode() : 0);
}
public override string ToString()
{
return current;
}
}
代码看起来有点丑,但是这个结构体的用法很有代表性。
Colors color1 = Colors.Red;
Console.WriteLine(color1); // #ff0000
Colors color2 = (Colors) "#00ff00";
Console.WriteLine(color2); // #00ff00
// Colors color3 = "#0000ff"; // Compilation error
// String color4 = Colors.Red; // Compilation error
Colors color5 = (Colors)"#ff0000";
Console.WriteLine(color1 == color5); // True
Colors color6 = (Colors)"#00ff00";
Console.WriteLine(color1 == color6); // False
此外,我认为,如果需要大量这样的枚举,可能会使用代码生成(例如T4)。
我创建了一个基类,用于在. net中创建字符串值的枚举。它只是一个c#文件,你可以复制粘贴到你的项目中,或者通过名为StringEnum的NuGet包安装。GitHub回购
如果类被注释为xml comment <completitionlist>,智能感知将提示枚举名称。(适用于c#和VB)
类似于普通enum的用法:
///<completionlist cref="HexColor"/>
class HexColor : StringEnum<HexColor>
{
public static readonly HexColor Blue = Create("#FF0000");
public static readonly HexColor Green = Create("#00FF00");
public static readonly HexColor Red = Create("#000FF");
}
// Static Parse Method
HexColor.Parse("#FF0000") // => HexColor.Red
HexColor.Parse("#ff0000", caseSensitive: false) // => HexColor.Red
HexColor.Parse("invalid") // => throws InvalidOperationException
// Static TryParse method.
HexColor.TryParse("#FF0000") // => HexColor.Red
HexColor.TryParse("#ff0000", caseSensitive: false) // => HexColor.Red
HexColor.TryParse("invalid") // => null
// Parse and TryParse returns the preexistent instances
object.ReferenceEquals(HexColor.Parse("#FF0000"), HexColor.Red) // => true
// Conversion from your `StringEnum` to `string`
string myString1 = HexColor.Red.ToString(); // => "#FF0000"
string myString2 = HexColor.Red; // => "#FF0000" (implicit cast)
Instalation:
将下面的StringEnum基类粘贴到项目中。(最新版本)
或者安装StringEnum NuGet包,它基于。net Standard 1.0,所以它可以运行在。net Core >= 1.0, . net Framework >= 4.5, Mono >= 4.6等等。
/// <summary>
/// Base class for creating string-valued enums in .NET.<br/>
/// Provides static Parse() and TryParse() methods and implicit cast to string.
/// </summary>
/// <example>
/// <code>
/// class Color : StringEnum <Color>
/// {
/// public static readonly Color Blue = Create("Blue");
/// public static readonly Color Red = Create("Red");
/// public static readonly Color Green = Create("Green");
/// }
/// </code>
/// </example>
/// <typeparam name="T">The string-valued enum type. (i.e. class Color : StringEnum<Color>)</typeparam>
public abstract class StringEnum<T> : IEquatable<T> where T : StringEnum<T>, new()
{
protected string Value;
private static Dictionary<string, T> valueDict = new Dictionary<string, T>();
protected static T Create(string value)
{
if (value == null)
return null; // the null-valued instance is null.
var result = new T() { Value = value };
valueDict.Add(value, result);
return result;
}
public static implicit operator string(StringEnum<T> enumValue) => enumValue.Value;
public override string ToString() => Value;
public static bool operator !=(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value != o2?.Value;
public static bool operator ==(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value == o2?.Value;
public override bool Equals(object other) => this.Value.Equals((other as T)?.Value ?? (other as string));
bool IEquatable<T>.Equals(T other) => this.Value.Equals(other.Value);
public override int GetHashCode() => Value.GetHashCode();
/// <summary>
/// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else throws InvalidOperationException.
/// </summary>
/// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
/// <param name="caseSensitive">If true, the strings must match case and takes O(log n). False allows different case but is little bit slower (O(n))</param>
public static T Parse(string value, bool caseSensitive = true)
{
var result = TryParse(value, caseSensitive);
if (result == null)
throw new InvalidOperationException((value == null ? "null" : $"'{value}'") + $" is not a valid {typeof(T).Name}");
return result;
}
/// <summary>
/// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else returns null.
/// </summary>
/// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
/// <param name="caseSensitive">If true, the strings must match case. False allows different case but is slower: O(n)</param>
public static T TryParse(string value, bool caseSensitive = true)
{
if (value == null) return null;
if (valueDict.Count == 0) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // force static fields initialization
if (caseSensitive)
{
if (valueDict.TryGetValue(value, out T item))
return item;
else
return null;
}
else
{
// slower O(n) case insensitive search
return valueDict.FirstOrDefault(f => f.Key.Equals(value, StringComparison.OrdinalIgnoreCase)).Value;
// Why Ordinal? => https://esmithy.net/2007/10/15/why-stringcomparisonordinal-is-usually-the-right-choice/
}
}
}