枚举和字符串匹配

时间:2013-02-26 00:31:42

标签: c# enums

我实际上是在尝试读取xml文件。其中一个值具有后缀,例如“30d”。这意味着'30天'。所以我试图将其转换为DateTime.Now.AddDays(30)。要在XML中读取此字段,我决定使用枚举:

enum DurationType { Min = "m", Hours = "h", Days = "d" }

现在我并不确定如何有效地处理这个问题(当涉及枚举时我有点愚蠢)。我应该首先将字符串中的后缀(在本例中为“d”)分开,然后使用enum语句尝试在switch中匹配它吗?

我想如果你对我的问题愚蠢,那就是:从30dDateTime.Now.AddDays(30)的最佳方式是什么?

7 个答案:

答案 0 :(得分:5)

这似乎是使用正则表达式的好地方;特别是捕获组。

以下是一个工作示例:

using System;
using System.Text.RegularExpressions;

namespace RegexCaptureGroups
{
    class Program
    {
        // Below is a breakdown of this regular expression:
        // First, one or more digits followed by "d" or "D" to represent days.
        // Second, one or more digits followed by "h" or "H" to represent hours.
        // Third, one or more digits followed by "m" or "M" to represent minutes.
        // Each component can be separated by any number of spaces, or none.
        private static readonly Regex DurationRegex = new Regex(@"((?<Days>\d+)d)?\s*((?<Hours>\d+)h)?\s*((?<Minutes>\d+)m)?", RegexOptions.IgnoreCase);

        public static TimeSpan ParseDuration(string input)
        {
            var match = DurationRegex.Match(input);

            var days = match.Groups["Days"].Value;
            var hours = match.Groups["Hours"].Value;
            var minutes = match.Groups["Minutes"].Value;

            int daysAsInt32, hoursAsInt32, minutesAsInt32;

            if (!int.TryParse(days, out daysAsInt32))
                daysAsInt32 = 0;

            if (!int.TryParse(hours, out hoursAsInt32))
                hoursAsInt32 = 0;

            if (!int.TryParse(minutes, out minutesAsInt32))
                minutesAsInt32 = 0;

            return new TimeSpan(daysAsInt32, hoursAsInt32, minutesAsInt32, 0);
        }

        static void Main(string[] args)
        {
            Console.WriteLine(ParseDuration("30d"));
            Console.WriteLine(ParseDuration("12h"));
            Console.WriteLine(ParseDuration("20m"));
            Console.WriteLine(ParseDuration("1d 12h"));
            Console.WriteLine(ParseDuration("5d 30m"));
            Console.WriteLine(ParseDuration("1d 12h 20m"));

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

编辑:下面是上面的一个替代,稍微更简洁的版本,虽然我不确定哪个更喜欢。我通常不喜欢过于密集的代码。 我调整了正则表达式,每个数字都限制了10位数。这允许我安全地使用int.Parse函数,因为我知道输入包含至少一个数字且最多十个(除非它根本没有捕获,在这种情况下它将是空字符串:因此,ParseInt32ZeroIfNullOrEmpty方法的目的)。

        // Below is a breakdown of this regular expression:
        // First, one to ten digits followed by "d" or "D" to represent days.
        // Second, one to ten digits followed by "h" or "H" to represent hours.
        // Third, one to ten digits followed by "m" or "M" to represent minutes.
        // Each component can be separated by any number of spaces, or none.
        private static readonly Regex DurationRegex = new Regex(@"((?<Days>\d{1,10})d)?\s*((?<Hours>\d{1,10})h)?\s*((?<Minutes>\d{1,10})m)?", RegexOptions.IgnoreCase);

        private static int ParseInt32ZeroIfNullOrEmpty(string input)
        {
            return string.IsNullOrEmpty(input) ? 0 : int.Parse(input);
        }

        public static TimeSpan ParseDuration(string input)
        {
            var match = DurationRegex.Match(input);

            return new TimeSpan(
                ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value),
                ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value),
                ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value),
                0);
        }

编辑:为了更进一步,我在下面添加了另一个版本,它处理天,小时,分钟,秒和毫秒,每个版本都有各种缩写。为了便于阅读,我将正则表达式拆分为多行。注意,我还必须在每个组件的末尾使用(\b|(?=[^a-z]))来调整表达式:这是因为“ms”单元被捕获为“m”单元。与“[^ a-z]”一起使用的“?=”的特殊语法表示匹配字符但不“消耗”它。

    // Below is a breakdown of this regular expression:
    // First, one to ten digits followed by "d", "dy", "dys", "day", or "days".
    // Second, one to ten digits followed by "h", "hr", "hrs", "hour", or "hours".
    // Third, one to ten digits followed by "m", "min", "minute", or "minutes".
    // Fourth, one to ten digits followed by "s", "sec", "second", or "seconds".
    // Fifth, one to ten digits followed by "ms", "msec", "millisec", "millisecond", or "milliseconds".
    // Each component may be separated by any number of spaces, or none.
    // The expression is case-insensitive.
    private static readonly Regex DurationRegex = new Regex(@"
        ((?<Days>\d{1,10})(d|dy|dys|day|days)(\b|(?=[^a-z])))?\s*
        ((?<Hours>\d{1,10})(h|hr|hrs|hour|hours)(\b|(?=[^a-z])))?\s*
        ((?<Minutes>\d{1,10})(m|min|minute|minutes)(\b|(?=[^a-z])))?\s*
        ((?<Seconds>\d{1,10})(s|sec|second|seconds)(\b|(?=[^a-z])))?\s*
        ((?<Milliseconds>\d{1,10})(ms|msec|millisec|millisecond|milliseconds)(\b|(?=[^a-z])))?",
        RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);

    private static int ParseInt32ZeroIfNullOrEmpty(string input)
    {
        return string.IsNullOrEmpty(input) ? 0 : int.Parse(input);
    }

    public static TimeSpan ParseDuration(string input)
    {
        var match = DurationRegex.Match(input);

        return new TimeSpan(
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value),
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value),
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value),
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Seconds"].Value),
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Milliseconds"].Value));
    }

答案 1 :(得分:3)

<强>更新 不要为此投票。我离开它只是因为它是一种替代方法。而是看看sa_ddam213和Dr. Wily的Apprentice的答案。

  

我应该将字符串中的后缀(在本例中为“d”)分开   首先,然后尝试使用switch语句在枚举中匹配它?

一个完整的例子:

private void button1_Click( object sender, EventArgs e ) {
    String value = "30d";

    Duration d = (Duration)Enum.Parse(typeof(Duration), value.Substring(value.Length - 1, 1).ToUpper());
    DateTime result = d.From(new DateTime(), value);

    MessageBox.Show(result.ToString());
}



enum Duration { D, W, M, Y };

static class DurationExtensions {
    public static DateTime From( this Duration duration, DateTime dateTime, Int32 period ) {
        switch (duration)
        {
          case Duration.D: return dateTime.AddDays(period);
          case Duration.W: return dateTime.AddDays((period*7));
          case Duration.M: return dateTime.AddMonths(period);
          case Duration.Y: return dateTime.AddYears(period);

          default: throw new ArgumentOutOfRangeException("duration");
        }
     }
    public static DateTime From( this Duration duration, DateTime dateTime, String fullValue ) {
        Int32 period = Convert.ToInt32(fullValue.ToUpper().Replace(duration.ToString(), String.Empty));
        return From(duration, dateTime, period);
    }
}

答案 2 :(得分:2)

我真的不知道在这里使用enum有何帮助。

以下是我如何处理它。

string s = "30d";

int typeIndex = s.IndexOfAny(new char[] { 'd', 'w', 'm' });
if (typeIndex > 0)
{
    int value = int.Parse(s.Substring(0, typeIndex));
    switch (s[typeIndex])
    {
        case 'd':
            result = DateTime.Now.AddDays(value);
            break;
        case 'w':
            result = DateTime.Now.AddDays(value * 7);
            break;
        case 'm':
            result = DateTime.Now.AddMonths(value);
            break;
    }
}

根据输入数据的可靠性,您可能需要使用int.TryParse()而不是int.Parse()。否则,这应该就是你所需要的。

注意:我还为.NET编写了sscanf()替换,可以很容易地处理这个问题。您可以在文章A sscanf() Replacement for .NET中看到相应的代码。

答案 3 :(得分:1)

尝试以下代码,假设像“30d”这样的值在字符串'val'中。

DateTime ConvertValue(string val) {
    if (val.Length > 0) {
        int prefix = Convert.ToInt32(val.Length.Remove(val.Length-1));
        switch (val[val.Length-1]) {
        case 'd': return DateTime.Now.AddDays(prefix);
        case 'm': return DateTime.Now.AddMonths(prefix);
        // etc.
    }
    throw new ArgumentException("string in unexpected format.");
}

答案 4 :(得分:1)

控制台应用程序示例/教程的示例:

enum DurationType
{
    [DisplayName("m")]
    Min = 1,
    [DisplayName("h")]
    Hours = 1 * 60,
    [DisplayName("d")]
    Days = 1 * 60 * 24
}

internal class Program
{
    private static void Main(string[] args)
    {

        string input1 = "10h";
        string input2 = "1d10h3m";

        var x = GetOffsetFromDate(DateTime.Now, input1);
        var y = GetOffsetFromDate(DateTime.Now, input2);

    }

    private static Dictionary<string, DurationType> suffixDictionary
    {
        get
        {
            return Enum
                .GetValues(typeof (DurationType))
                .Cast<DurationType>()
                .ToDictionary(duration => duration.GetDisplayName(), duration => duration);
        }
    }

    public static DateTime GetOffsetFromDate(DateTime date, string input)
    {
        MatchCollection matches = Regex.Matches(input, @"(\d+)([a-zA-Z]+)");
        foreach (Match match in matches)
        {
            int numberPart = Int32.Parse(match.Groups[1].Value);
            string suffix = match.Groups[2].Value;
            date = date.AddMinutes((int)suffixDictionary[suffix]);
        }
        return date;
    }


}


[AttributeUsage(AttributeTargets.Field)]
public class DisplayNameAttribute : Attribute
{
    public DisplayNameAttribute(String name)
    {
        this.name = name;
    }
    protected String name;
    public String Name { get { return this.name; } }
}

public static class ExtensionClass
{
    public static string GetDisplayName<TValue>(this TValue value) where TValue : struct, IConvertible
    {
        FieldInfo fi = typeof(TValue).GetField(value.ToString());
        DisplayNameAttribute attribute = (DisplayNameAttribute)fi.GetCustomAttributes(typeof(DisplayNameAttribute), false).FirstOrDefault();
        if (attribute != null)
            return attribute.Name;
        return value.ToString();
    }
}

使用属性来定义后缀,使用枚举值来定义偏移量。

需要:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

使用枚举整数值可能会被认为是一个黑客攻击,但是这个例子仍然可以让你解析掉所有的枚举(对于任何其他用途,比如switch case),只需要很少的调整。

答案 5 :(得分:0)

无法使用非数字类型支持枚举,因此基于字符串的枚举不可用。你可能会过度思考它。在不了解问题的情况下,最直接的解决方案似乎是将最后一个字符拆分,将其余字符转换为int,然后将每个最终字符作为单独的案例处理。

答案 6 :(得分:0)

我建议先使用regexp去除数字,然后执行Enum.Parse Method来评估枚举的值。您可以使用开关(请参阅Corylulu的答案)根据解析的数字和枚举值获得正确的偏移量。