【问题标题】:Refactoring big switch case to set property重构大开关盒以设置属性
【发布时间】:2017-02-04 12:57:50
【问题描述】:

大家好,这是我的第一个问题,希望它是好的\o/

所以,首先是 c# 应用程序通过套接字不断接收数据,第一次连接到服务器并且我发出请求时,我收到了这个大字符串(包含 100 多个字段),为了这个例子我会把它变小:

0:7:1:Augusto:2:Question:3:Stackoverflow:4:Question:5:201609262219:6:stuff:7:stuff:..:100:!

在收到这个大字符串之后,我只会收到更新,例如:

0:7:3:Changed Topic:4:Doubt:5:2016092710:100:!

字段 100 将始终出现,因为它定义了字符串终止符。
当我收到这个大字符串时,我必须构建一个对象 Question,这个大字符串总是会产生一个 Question 对象,所以我们有:

public class Question 
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Action { get; set; }
    public string Topic { get; set; }
    public string Body { get; set; }
    public string Time { get; set; }
    ...
    public string 99 { get; set; }
}

为了构建问题对象,我这样做了:

public void Process(string data) 
{
    Question question = new Question();
    String[] fields = data.split(“:”);

    for(int i = 0; i < fields.Length; i +=2) {
        Switch (fields[i]) {
            case "0":
                question.Id = fields[i+1]
                    break;

            case "1":
                question.Name = fields[i+1]
                    break;

            case "2":
                question.Action = fields[i+1]
                    break;
            case "3":
                question.Topic = fields[i+1]
                    break;

            case "4":
                question.Body = fields[i+1]
                    break;
            case "5":
                question.Time = fields[i+1]
                    break;
                ...
            case "99":
                    Question.99 = fields[i+1]
                        break;

        }   
    }
}

我研究了 stackoverflow 的答案,但它们与我想要实现的不同。
我认为使用策略模式或命令模式不值得,因为逻辑很简单,使用这种模式会增加太多复杂性。
我不确定Dictionary&lt;string,Func&lt;string&gt;&gt; 是否能解决我的问题,我认为这与女巫案相同。
我也考虑过使用反射(setValue),但我认为它会很慢,我真的需要这个东西才能飞。

这里的问题是我只是给对象一个值,我不想做这个大的开关盒。我认为我创建对象的方式有些错误,但我无法找到不同的解决方案。

还有一个问题,你们认为string.Split() 会成为我代码的瓶颈吗?因为我多次调用 Process(data) 方法,比如每秒 100 次。

谢谢!

--- 更新
固定样本和案例“5”。

【问题讨论】:

  • 你能修复你的样品吗?字符串0:7:3:Changed Topic4:Doubt:5:2016092710:19:100:! 显然无效。
  • 固定为 0:7:3:Changed Topic:4:Doubt:5:2016092710:100:!
  • 将事情分解成更小更易于管理的类并不会使事情变得复杂。如果有的话,试图阅读和理解 switch 语句在 99 种不同的潜在情况下是如何工作的,是很难理解和理解的。我为 IRC 客户端做了类似的事情,只是实现了一个适当的模式来解决问题。存在模式来清理事物。通常,如果您发现该模式很复杂,那么您可能没有以正确的方式使用它。
  • 许多方法来解决这个问题,如果不是全部的话,大多数方法都在 Stack Overflow 上的类似问题中解决。请花一些时间查看其他问题中已经提出的建议,如果您仍有问题,请发布一个新问题,在其中解释您所做的研究以及具体需要帮助的内容.
  • @JohnathonSullinger 如果我错了请纠正我,但是使用模式(命令和策略)将为类的每个属性添加一个类,对吗?我同意你的观点,99 个 switch case 很糟糕,但我认为 99 个类会写得最差?你能提供你使用的设计模式吗?

标签: c# switch-statement refactoring


【解决方案1】:

我认为您担心在这种情况下过早优化。一般的经验法则是 - 除非性能问题实际上是可衡量的,否则不要优化。

考虑到这一点,这个问题可以在一个相当优雅的庄园中解决。我将使用一点 cached 反射,以便我只提取一次 PropertyInfo 及其自定义属性。因此,您会在初始连接上看到很小的性能开销。在这一点上,这是非常快的。

因为我多次调用 Process(data) 方法,比如每秒 100 次。

我在具有 4 个内核和 8GB 内存的 Windows 10 虚拟机中运行此程序。我对包含 100 个值的字符串进行了 10,000 次迭代(没有使用缩短的字符串),我看到每个字符串的平均解析时间为 516.32 ticks(每 1ms 10,000 个滴答声)。考虑到您的要求是 1 秒内 100 个字符串,这对于您的吞吐量应该足够快。我能够在 1 秒的时间范围内解析超过 20,000 个。每秒解析的次数最终将高于我处理的 20,000 次,因为我每次都使用整个 100 个元素字符串而不是较小的更新字符串。

长期运行可能会有一些 GC 压力,我必须运行测试才能看到。这可以通过引入几个对象池来减少分配来优化。无论您采用哪种方法,您都会遇到此问题,仅仅是因为您必须将 1 个字符串解析为多个值,因此此解决方案不会尝试解决此问题。

让我们从包含 100 个元素的字符串开始。

0:FizBam Foo Bar:1:FizBam Foo Bar:2:FizBam Foo Bar:3:FizBam Foo Bar:4:FizBam Foo Bar:5:FizBam Foo Bar:6:FizBam Foo Bar:7:FizBam Foo Bar:8:FizBam Foo Bar:9:FizBam Foo Bar:10:FizBam Foo Bar:11:FizBam Foo Bar:12:FizBam Foo Bar:13:FizBam Foo Bar:14:FizBam Foo Bar:15:FizBam Foo Bar:16:FizBam Foo Bar:17:FizBam Foo Bar:18:FizBam Foo Bar:19:FizBam Foo Bar:20:FizBam Foo Bar:21:FizBam Foo Bar:22:FizBam Foo Bar:23:FizBam Foo Bar:24:FizBam Foo Bar:25:FizBam Foo Bar:26:FizBam Foo Bar:27:FizBam Foo Bar:28:FizBam Foo Bar:29:FizBam Foo Bar:30:FizBam Foo Bar:31:FizBam Foo Bar:32:FizBam Foo Bar:33:FizBam Foo Bar:34:FizBam Foo Bar:35:FizBam Foo Bar:36:FizBam Foo Bar:37:FizBam Foo Bar:38:FizBam Foo Bar:39:FizBam Foo Bar:40:FizBam Foo Bar:41:FizBam Foo Bar:42:FizBam Foo Bar:43:FizBam Foo Bar:44:FizBam Foo Bar:45:FizBam Foo Bar:46:FizBam Foo Bar:47:FizBam Foo Bar:48:FizBam Foo Bar:49:FizBam Foo Bar:50:FizBam Foo Bar:51:FizBam Foo Bar:52:FizBam Foo Bar:53:FizBam Foo Bar:54:FizBam Foo Bar:55:FizBam Foo Bar:56:FizBam Foo Bar:57:FizBam Foo Bar:58:FizBam Foo Bar:59:FizBam Foo Bar:60:FizBam Foo Bar:61:FizBam Foo Bar:62:FizBam Foo Bar:63:FizBam Foo Bar:64:FizBam Foo Bar:65:FizBam Foo Bar:66:FizBam Foo Bar:67:FizBam Foo Bar:68:FizBam Foo Bar:69:FizBam Foo Bar:70:FizBam Foo Bar:71:FizBam Foo Bar:72:FizBam Foo Bar:73:FizBam Foo Bar:74:FizBam Foo Bar:75:FizBam Foo Bar:76:FizBam Foo Bar:77:FizBam Foo Bar:78:FizBam Foo Bar:79:FizBam Foo Bar:80:FizBam Foo Bar:81:FizBam Foo Bar:82:FizBam Foo Bar:83:FizBam Foo Bar:84:FizBam Foo Bar:85:FizBam Foo Bar:86:FizBam Foo Bar:87:FizBam Foo Bar:88:FizBam Foo Bar:89:FizBam Foo Bar:90:FizBam Foo Bar:91:FizBam Foo Bar:92:FizBam Foo Bar:93:FizBam Foo Bar:94:FizBam Foo Bar:95:FizBam Foo Bar:96:FizBam Foo Bar:97:FizBam Foo Bar:98:FizBam Foo Bar:99:FizBam Foo Bar:100:!

然后我创建了一个Attribute,我可以使用它来将元素映射到模型属性。

public class MapAttribute : Attribute
{
    public MapAttribute(string fieldKey)
    {
        this.Field = fieldKey;
    }

    public string Field { get; private set; }

    public PropertyInfo Property { get; set; }

    public void SetValue(Question question, string value)
    {
        this.Property.SetValue(question, value);
    }
}

现在,在您的模型上,您只需将属性映射到传入字段即可。当一个字段进入时,您将它与问题的实例一起传递给属性并让它分配值。您也可以在某种查找表中处理此问题,但属性似乎是允许您向模型添加新属性并在单个位置更新映射的最简单方法。

我在这里展示了整个模型,其中有 100 个属性映射到服务器响应。

public class Question
{
    [Map("0")]
    public string FooBar { get; set; }

    [Map("1")]
    public string Id { get; set; }

    [Map("2")]
    public string Action { get; set; }

    [Map("3")]
    public string Topic { get; set; }

    [Map("4")]
    public string Body { get; set; }

    [Map("5")]
    public string Time { get; set; }

    [Map("6")]
    public string Query { get; set; }

    [Map("7")]
    public string Answer { get; set; }

    [Map("8")]
    public string __8 { get; set; }

    [Map("9")]
    public string __9 { get; set; }

    [Map("10")]
    public string __10 { get; set; }

    [Map("11")]
    public string __11 { get; set; }

    [Map("12")]
    public string __12 { get; set; }

    [Map("13")]
    public string __13 { get; set; }

    [Map("14")]
    public string __14 { get; set; }

    [Map("15")]
    public string __15 { get; set; }

    [Map("16")]
    public string __16 { get; set; }

    [Map("17")]
    public string __17 { get; set; }

    [Map("18")]
    public string __18 { get; set; }

    [Map("19")]
    public string __19 { get; set; }

    [Map("20")]
    public string __20 { get; set; }

    [Map("21")]
    public string __21 { get; set; }

    [Map("22")]
    public string __22 { get; set; }

    [Map("23")]
    public string __23 { get; set; }

    [Map("24")]
    public string __24 { get; set; }

    [Map("25")]
    public string __25 { get; set; }

    [Map("26")]
    public string __26 { get; set; }

    [Map("27")]
    public string __27 { get; set; }

    [Map("28")]
    public string __28 { get; set; }

    [Map("29")]
    public string __29 { get; set; }

    [Map("30")]
    public string __30 { get; set; }

    [Map("31")]
    public string __31 { get; set; }

    [Map("32")]
    public string __32 { get; set; }

    [Map("33")]
    public string __33 { get; set; }

    [Map("34")]
    public string __34 { get; set; }

    [Map("35")]
    public string __35 { get; set; }

    [Map("36")]
    public string __36 { get; set; }

    [Map("37")]
    public string __37 { get; set; }

    [Map("38")]
    public string __38 { get; set; }

    [Map("39")]
    public string __39 { get; set; }

    [Map("40")]
    public string __40 { get; set; }

    [Map("41")]
    public string __41 { get; set; }

    [Map("42")]
    public string __42 { get; set; }

    [Map("43")]
    public string __43 { get; set; }

    [Map("44")]
    public string __44 { get; set; }

    [Map("45")]
    public string __45 { get; set; }

    [Map("46")]
    public string __46 { get; set; }

    [Map("47")]
    public string __47 { get; set; }

    [Map("48")]
    public string __48 { get; set; }

    [Map("49")]
    public string __49 { get; set; }

    [Map("50")]
    public string __50 { get; set; }

    [Map("51")]
    public string __51 { get; set; }

    [Map("52")]
    public string __52 { get; set; }

    [Map("53")]
    public string __53 { get; set; }

    [Map("54")]
    public string __54 { get; set; }

    [Map("55")]
    public string __55 { get; set; }

    [Map("56")]
    public string __56 { get; set; }

    [Map("57")]
    public string __57 { get; set; }

    [Map("58")]
    public string __58 { get; set; }

    [Map("59")]
    public string __59 { get; set; }

    [Map("60")]
    public string __60 { get; set; }

    [Map("61")]
    public string __61 { get; set; }

    [Map("62")]
    public string __62 { get; set; }

    [Map("63")]
    public string __63 { get; set; }

    [Map("64")]
    public string __64 { get; set; }

    [Map("65")]
    public string __65 { get; set; }

    [Map("66")]
    public string __66 { get; set; }

    [Map("67")]
    public string __67 { get; set; }

    [Map("68")]
    public string __68 { get; set; }

    [Map("69")]
    public string __69 { get; set; }

    [Map("70")]
    public string __70 { get; set; }

    [Map("71")]
    public string __71 { get; set; }

    [Map("72")]
    public string __72 { get; set; }

    [Map("73")]
    public string __73 { get; set; }

    [Map("74")]
    public string __74 { get; set; }

    [Map("75")]
    public string __75 { get; set; }

    [Map("76")]
    public string __76 { get; set; }

    [Map("77")]
    public string __77 { get; set; }

    [Map("78")]
    public string __78 { get; set; }

    [Map("79")]
    public string __79 { get; set; }

    [Map("80")]
    public string __80 { get; set; }

    [Map("81")]
    public string __81 { get; set; }

    [Map("82")]
    public string __82 { get; set; }

    [Map("83")]
    public string __83 { get; set; }

    [Map("84")]
    public string __84 { get; set; }

    [Map("85")]
    public string __85 { get; set; }

    [Map("86")]
    public string __86 { get; set; }

    [Map("87")]
    public string __87 { get; set; }

    [Map("88")]
    public string __88 { get; set; }

    [Map("89")]
    public string __89 { get; set; }

    [Map("90")]
    public string __90 { get; set; }

    [Map("91")]
    public string __91 { get; set; }

    [Map("92")]
    public string __92 { get; set; }

    [Map("93")]
    public string __93 { get; set; }

    [Map("94")]
    public string __94 { get; set; }

    [Map("95")]
    public string __95 { get; set; }

    [Map("96")]
    public string __96 { get; set; }

    [Map("97")]
    public string __97 { get; set; }

    [Map("98")]
    public string __98 { get; set; }

    [Map("99")]
    public string __99 { get; set; }

    [Map("100")]
    public string __100 { get; set; }
}

现在,当建立第一个连接时,获取所有属性及其属性并将它们缓存在字典中。

Dictionary<string, MapAttribute> properties = typeof(Question).GetProperties().ToDictionary(
    property => property.GetCustomAttribute<MapAttribute>().Field,
    property =>
    {
        var attribute = property.GetCustomAttribute<MapAttribute>();
        attribute.Property = property;
        return attribute;
    });

在应用的生命周期内只执行一次。您将在从服务器获得的每个响应中重复使用该字典。现在,当您收到来自服务器的更新时,您只需使用属性对其进行解析。

void Parse(string message, Dictionary<string, MapAttribute> fieldMapping)
{
    string[] messageContent = message.Split(':');
    var question = new Question();
    for (int index = 0; index < messageContent.Length; index++)
    {
        string field = messageContent[index];
        MapAttribute mapping = fieldMapping[field];
        index++;
        mapping.SetValue(question, messageContent[index]);
    }
}

这里没有什么花哨的东西。我们刚刚将您的 switch 语句替换为字典查找和用于处理将响应键映射到模型属性的属性。极大地减少了您必须编写的最终代码,并让您将来只需使用新属性更新您的模型。简单且易于维护。您可以通过不太担心性能来实现这一点,直到性能真正成为一个可衡量的问题。

【讨论】:

  • 来吧,你真的不必粘贴整个“模型”类,我想人们会明白的。 :) 除此之外,如果性能是关键(每秒 100 条消息并不多),您还可以将 PropertyInfo.SetValue 替换为 compiled delegate
  • @Groo 网站指南是完整的、完整的示例。这是我提供的 :) 任何人都可以复制/粘贴并运行它,而无需添加所有属性,以验证我的性能数字。
  • @JohnathonSullinger 感谢您的优雅回答!下次我会把我的懒惰放在一边,把所有的东西都写下来。每秒 100 条消息不是一个很好的例子,但正如你所说,它可以处理 20,000 条消息,所以我想我不必担心。以前我是通过反射来做的,但是代码运行很慢,我相信缓存属性会大大加快代码速度。
  • 是的,缓存将产生巨大的影响。反映每个请求的属性肯定会降低您的吞吐量。通过在MapAttribute 中更新我的 SetValue 方法以使用 Groo 提到的编译委托,可以提高性能。您还可以通过为问题创建对象池来增加它,重新使用问题模型对象而不是在每个响应上进行分配。您还可以循环遍历传入字符串中的每个字符,而不是拆分。在仍然使用这种方法的同时,您可以改进几个方面
  • @AugustoMelo 如果它解决了您的问题,请务必将其标记为您问题的答案。这将有助于让其他人知道对于那些稍后寻求相同建议的人来说,哪个答案最适合您的问题
【解决方案2】:

你可以这样重构你的代码:

public void Process(string data)
{
    var assignments = new Dictionary<string, Action<Question, string>>()
    {
        { "0", (q, t) => q.Id = t },
        { "1", (q, t) => q.Name = t },
        // ...
        { "99",  (q, t) => q._99 = t },
    };

    Question question = new Question();
    string[] fields = data.Split(':');

    for (int i = 0; i < fields.Length; i += 2)
    {
        assignments[fields[i]](question, fields[i + 1]);
    }
}

这消除了切换,并允许您在运行时重构字典。

我建议也许这是一个更好的方法:

public class Question
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Action { get; set; }
    public string Topic { get; set; }
    public string Body { get; set; }
    public string Time { get; set; }
    public string _99 { get; set; }

    private static Dictionary<string, Action<Question, string>> __assignments = new Dictionary<string, Action<Question, string>>()
    {
        { "0", (q, t) => q.Id = t },
        { "1", (q, t) => q.Name = t },
        // ...
        { "99",  (q, t) => q._99 = t },
    };

    public void SetProperty(string key, string value)
    {
        __assignments[key](this, value);
    }
}

public void Process(string data)
{
    Question question = new Question();
    string[] fields = data.Split(':');

    for (int i = 0; i < fields.Length; i += 2)
    {
        question.SetProperty(fields[i], fields[i + 1]);
    }
}

而且,作为一个小替代方案,您可以这样定义 __assignments 以使其更简洁:

    private static Dictionary<string, Action<Question, string>> __assignments =
        new Action<UserQuery.Question, string>[]
        {
            (q, t) => q.Id = t,
            (q, t) => q.Name = t,
            // ...
            (q, t) => q._99 = t,
        }
            .Select((x, n) => new { x, n })
            .ToDictionary(xn => xn.n.ToString(), xn => xn.x);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-28
    • 1970-01-01
    • 2022-11-29
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多