C# 枚举转换,最干净的方式

C# enum conversion, cleanest way

背景

具有 delphi 背景,我习惯于使用常量和常量数组等来修复很多问题。此外 Delphi allpws 使用助手 类 对枚举进行类型转换。 现在看一下 C#

中的这些枚举
  public enum DateInterval { Off, Day, Month, Year };
  public enum TimeInterval { Off, MilliSecond, Second, Minute, Hour };
  public enum DateTimeInterval { Off, MilliSecond, Second, Minute, Hour, Day, Month, Year };

如您所见,这些枚举之间可以进行逻辑转换,我已经成功地使用以下方法实现了这一点:

 public static class DateIntervalHelper 
    {
        public static DateTimeInterval ToDateTimeInterval(this TimeInterval aInterval)
        {
            switch (aInterval)
            {
                case TimeInterval.MilliSecond:
                    return DateTimeInterval.MilliSecond;
                case TimeInterval.Second:
                    return DateTimeInterval.Second;
                case TimeInterval.Hour:
                    return DateTimeInterval.Hour;
                default: // ivOff
                    return DateTimeInterval.Off;
            }
        }
        public static DateTimeInterval ToDateTimeInterval(this DateInterval aInterval)
        {
            switch (aInterval)
            {
                case DateInterval.Day:
                    return DateTimeInterval.Day;
                case DateInterval.Month:
                    return DateTimeInterval.Month;
                case DateInterval.Year:
                    return DateTimeInterval.Year;
                default: // ivOff
                    return DateTimeInterval.Off;
            }
        }
    }

在delphi我宁愿做这样的事情

const 
  cDate2DateTimeInterval:array[DateInterval] of DateTimeInterval=(
    DateTimeInterval.Off,
    DateTimeInterval.Day,
    DateTimeInterval.Month,
    DateTimeInterval.Year);

  cTime2DateTimeInterval:array[TimeInterval] of DateTimeInterval=(
    DateTimeInterval.Off,
    DateTimeInterval.MilliSecond,
    DateTimeInterval.Second,
    DateTimeInterval.Minute,
    DateTimeInterval.Hour);

然后使用这些数组来“映射”转换。 (可能会出现一些 Snytax™ 错误,但您会明白这一点)

问题

Wat 是在 C# 中使用 Core3.1 实现此转换的更简洁的方法吗?

由于名称相同,您可以执行以下操作

public static class DateIntervalHelper 
{
    public static DateTimeInterval ToDateTimeInterval(this TimeInterval aInterval)
    {
        if (Enum.TryParse<DateTimeInterval>(aInterval.ToString(), out var @enum))
            return @enum;

        return DateTimeInterval.Off;
    }
    public static DateTimeInterval ToDateTimeInterval(this DateInterval aInterval)
    {
        if (Enum.TryParse<DateTimeInterval>(aInterval.ToString(), out var @enum))
            return @enum;

        return DateTimeInterval.Off;
    }
}

示例:

https://dotnetfiddle.net/bXlBfW

一个真正好的方法是使用最近推出的 EnumConverter。它用于从任何 Enum 转换为任何其他 type(还有另一个 Enum)。 EnumConverter 继承了众所周知的 TypeConverter.

您可以在此处查看文档:https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.enumconverter?view=netcore-3.1

从文档中复制粘贴的示例:

Enum myServer = Servers.Exchange;
string myServerString = "BizTalk";
Console.WriteLine(TypeDescriptor.GetConverter(myServer).ConvertTo(myServer, typeof(string))); 
Console.WriteLine(TypeDescriptor.GetConverter(myServer).ConvertFrom(myServerString)); 

当然你可以采用手动方式,建立一个 Dictionary 和 return 帮助程序 class.

中每个键的值

type/enum 转换器的最大优点如下:如果您的代码中有一些地方您不知道源类型或目标类型,您可以使用 TypeDescriptor.GetConverter 并将 <U> 转换为 <T>.

您可以编写一个通用转换器:

static bool TryConvert<TSourceEnum, TDestEnum>(TSourceEnum source, out TDestEnum result)
    where TSourceEnum : struct, Enum
    where TDestEnum : struct, Enum
{
    if (Enum.TryParse(source.ToString(), out TDestEnum r))
    {
        result = r;
        return true;
    }
    result = default;
    return false;
}

用法:

if (TryConvert(DateInterval.Off, out TimeInterval timeInterval))
{
    // Do something with your time interval
}

此处将源枚举值的字符串表示形式解析为目标枚举值。

这可能不是最迷人的解决方案,但我认为它具有您所说的 array/map 那种感觉。使用将一种类型映射到另一种类型的字典。您可以通过翻转类型来创建另一个字典以向后移动。用法在下面的“测试”方法中显示。

public static Dictionary<DateInterval, DateTimeInterval> DateToDateTime = new Dictionary<DateInterval, DateTimeInterval>()
    {
        { DateInterval.Off, DateTimeInterval.Off},
        { DateInterval.Day, DateTimeInterval.Day},
        { DateInterval.Month, DateTimeInterval.Month},
        { DateInterval.Year, DateTimeInterval.Year}
    };

    public static void Test()
    {
        //This acts kind of like an array/map
        DateTimeInterval converted = DateToDateTime[DateInterval.Day];
    }

基于 Kd 的回答,并且仍然使用 delphistyle 映射数组方法(我可以,因为我的枚举是连续的)我想到了这个:

public enum DateInterval { Off, Day, Month, Year };
public enum TimeInterval { Off, MilliSecond, Second, Minute, Hour };
public enum DateTimeInterval { Off, MilliSecond, Second, Minute, Hour, Day, Month, Year };


public static class DateIntervalHelper
{
    private static readonly DateTimeInterval[] dateIntervalMap = 
    { 
        DateTimeInterval.Off, DateTimeInterval.Day, DateTimeInterval.Month, DateTimeInterval.Year 
    };
    private static readonly DateTimeInterval[] timeIntervalMap = 
    { 
        DateTimeInterval.Off, DateTimeInterval.MilliSecond, DateTimeInterval.Second, DateTimeInterval.Minute, DateTimeInterval.Hour 
    };

    public static DateTimeInterval ToDateTimeInterval(this TimeInterval aInterval) 
      => timeIntervalMap[(int)aInterval];
    public static DateTimeInterval ToDateTimeInterval(this DateInterval aInterval)
      => dateIntervalMap[(int)aInterval];
}

我实际测试过这个并且可以确认它有效:>

使用这个也很简单,查看源码末尾的重载函数:

    public static DateTime StartOfInterval(DateTime aInput, DateTimeInterval aInterval)
    {
        switch (aInterval)
        {
            case DateTimeInterval.MilliSecond:
                return new DateTime(aInput.Year, aInput.Month, aInput.Day, aInput.Hour, aInput.Minute, aInput.Second, aInput.Millisecond);
            case DateTimeInterval.Second:
                return new DateTime(aInput.Year, aInput.Month, aInput.Day, aInput.Hour, aInput.Minute, aInput.Second, 0);
            case DateTimeInterval.Minute:
                return new DateTime(aInput.Year, aInput.Month, aInput.Day, aInput.Hour, aInput.Minute, 0, 0);
            case DateTimeInterval.Hour:
                return aInput.BeginOfHour();
            case DateTimeInterval.Day:
                return aInput.BeginOfDay();
            case DateTimeInterval.Month:
                return aInput.BeginOfMonth();
            case DateTimeInterval.Year:
                return aInput.BeginOfYear();
            default: // ivOff
                return aInput;
        }
    }
    public static DateTime StartOfInterval(DateTime aInput, DateInterval aInterval)
    {
        return StartOfInterval(aInput, aInterval.ToDateTimeInterval());
    }
    public static DateTime StartOfInterval(DateTime aInput, TimeInterval aInterval)
    {
        return StartOfInterval(aInput, aInterval.ToDateTimeInterval());
    }