分配给新对象时切换或字典


12

最近,我开始倾向于使用Dictionaries而不是Switch语句来映射1-1关系。我发现它的编写速度更快,并且在心理上也更容易处理。不幸的是,当映射到对象的新实例时,我不想这样定义它:

var fooDict = new Dictionary<int, IBigObject>()
{
    { 0, new Foo() }, // Creates an instance of Foo
    { 1, new Bar() }, // Creates an instance of Bar
    { 2, new Baz() }  // Creates an instance of Baz
}

var quux = fooDict[0]; // quux references Foo

有了这种构造,我浪费了CPU周期和内存来创建3个对象,而做它们的构造函数可能包含的一切,而最终只使用其中一个。我还相信,fooDict[0]在这种情况下将其他对象映射到它们会导致它们引用同一事物,而不是Foo按预期方式创建新的实例。一种解决方案是改用lambda:

var fooDict = new Dictionary<int, Func<IBigObject>>()
{
    { 0, () => new Foo() }, // Returns a new instance of Foo when invoked
    { 1, () => new Bar() }, // Ditto Bar
    { 2, () => new Baz() }  // Ditto Baz
}

var quux = fooDict[0](); // equivalent to saying 'var quux = new Foo();'

这会变得太混乱了吗?最后很容易错过()。还是映射到函数/表达式是很普遍的做法?另一种选择是使用一个开关:

IBigObject quux;
switch(someInt)
{
    case 0: quux = new Foo(); break;
    case 1: quux = new Bar(); break;
    case 2: quux = new Baz(); break;
}

哪个调用更可接受?

  • 字典,用于更快的查找和更少的关键字(大小写和中断)
  • 开关:在代码中更常见,不需要间接使用Func <>对象。

2
如果没有lambda,则每次使用相同的键进行查找时,您都会返回相同的实例(如所述fooDict[0] is fooDict[0])。使用lambda和switch都不是这种情况
棘轮怪胎

@ratchetfreak是的,我实际上在输入示例时就意识到了这一点。我想我在某处记了一下。
KChaloux 2012年

1
我猜想,您明确地将其放在Constant中的事实意味着您需要创建的对象是可变的。但是如果有一天您可以使它们不变,那么直接返回对象将是最好的解决方案。您可以将dict放在const字段中,而在整个应用程序中只产生一次创建成本。
Laurent Bourgault-Roy 2014年

Answers:


7

这是对工厂模式的一个有趣的看法。我喜欢字典和lambda表达式的结合;它使我以一种新的方式看待那个容器。

正如您在评论中提到的那样,我无视您对CPU周期的担忧,即非lambda方法无法满足您的需求。

我认为这两种方法(切换与字典+ lambda)都可以。唯一的限制是,通过使用字典,您将限制为了生成返回的类而可以接收的输入类型。

使用switch语句将为您提供输入参数的更大灵活性。但是,如果这成为问题,则可以将Dictionary包装在方法内部,并获得相同的最终结果。

如果对您的团队来说是新的,请注释代码并说明发生了什么。要求进行团队代码审查,引导他们完成工作并让他们知道。除此之外,它看起来还不错。


不幸的是,大约一个月前,我的团队仅由我组成(领导退出了)。我没想到它与工厂模式有关。实际上,这是一个整洁的观察。
KChaloux 2012年

1
@KChaloux:当然,如果你只使用Factory Method模式,你case 0: quux = new Foo(); break;成为case 0: return new Foo();这简直是一场容易编写和比更容易阅读{ 0, () => new Foo() }
PDR

@pdr已经在代码中显示了几个地方。在激发这个问题的对象上创建工厂方法可能是一个很好的理由,但我认为它很有趣,可以单独提出。
KChaloux 2012年

1
@KChaloux:我承认我并不热衷于最近对用字典替换switch / case的痴迷。我还没有看到用自己的方法简化和隔离开关不会更加有效的情况。
pdr 2012年

@pdr迷恋是在这里使用的词。在决定如何处理值之间的一次性映射时,需要更多考虑。我同意,在重复执行此操作的情况下,最好隔离创建方法。
KChaloux 2012年

7

C#4.0为您提供了Lazy<T>该类,该类与您自己的第二个解决方案相似,但是更加明确地大喊“惰性初始化”。

var fooDict = new Dictionary<int, Lazy<IBigObject>>()
{
    { 0, new Lazy(() => new Foo()) }, // Returns a new instance of Foo when invoked
    { 1, new Lazy(() => new Bar()) }, // Ditto Bar
    { 2, new Lazy(() => new Baz()) }  // Ditto Baz
}

哦,我不知道。
KChaloux 2012年

哦,那很好!
Laurent Bourgault-Roy 2014年

2
但是,一旦调用Lazy.Value,它将在其生命周期内使用相同的实例。见延迟初始化
丹里昂

当然,否则它不会是延迟的初始化,而只是每次都重新初始化。
Avner Shahar-Kashtan'2

OP指出他每次都需要它来创建新实例。带有lambda的第二个解决方案和带有开关的第三个解决方案都可以这样做,而第一个解决方案和Lazy <T>实现则没有。
丹·里昂斯2014年

2

从风格上讲,我认为它们之间的可读性是相同的。使用进行依赖注入更加容易Dictionary

不要忘记,使用时必须检查密钥是否存在Dictionary,如果不存在则必须提供备用。

我更喜欢switch静态代码路径和Dictionary动态代码路径的语句(可以在其中添加或删除条目)。编译器可能能够使用进行某些静态优化,而switch不能使用Dictionary

有趣的是,这种Dictionary模式是人们有时在Python中所做的,因为Python缺少该switch语句。否则,它们使用if-else链。


1

总的来说,我都不愿意。

无论消耗什么,都应该使用Func<int, IBigObject>。然后,映射的源可以是Dictionary或具有switch语句,Web服务调用或某些文件查找...的方法。

至于实现,我更喜欢使用Dictionary,因为它可以更容易地从“硬代码字典,查找关键字,返回结果”重构为“从文件,查找关键字,返回结果加载字典”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.