【问题标题】:What is generic pattern matching for in c# 7.1?c# 7.1 中的通用模式匹配是什么?
【发布时间】:2020-02-14 04:08:58
【问题描述】:

C# 7.1 中引入了一个新功能,称为通用模式匹配。我发现的其中一个示例如下所示:

void Attack(IWeapon weapon, IEnemy enemy)
{
    switch (weapon)
    {
        case Sword sword:
            // process sword attack
            break;
        case Bow bow:
            // process bow attack
            break;
    }
}

但在我看来,这是一个不正确的设计,违反了第二个 SOLID 原则(开闭)。我什至无法想到可能需要这样做的情况,根据我的理解,如果您遇到需要这种开关的情况,那么您做错了什么。另一方面,如果将此功能添加到语言中,则必须有充分的理由这样做。所以问题是——假设你没有创建糟糕的架构,你什么时候需要这个。

【问题讨论】:

  • 它违反了它,它强制您编辑代码以支持额外类型的IWeapon(代码未关闭以供修改)。
  • @KorsaR 这与 OCP 无关,也不违反 OCP。具体的武器类没有被修改——事实上,每次你想要编写一个新的基于类型的函数时,它们不必被修改。是的,添加模式匹配和其他功能特性有 非常 充分的理由。它们减少脆弱性,它们强迫您修改Weapon,它们允许您编写编译器本身可以验证是否正常工作的代码。没有它,您将不得不依赖运行时验证和强制转换来获得正确的武器类型
  • 我不能同意,要添加新类型您必须修改代码,IWeapon 只是一个字谜,方法相反,它应该在IWeapon 上。但是针对具体类型检查IWeapon 直接违反了OCP。您不能在不修改代码的情况下将任何新类型的IWeapon 添加到代码中,从而使IWeapon 无用。至少如果它检查 ISwingableObjectIShootableObject 我会同意它在某种程度上符合 OCP,即使设计很差。
  • OCP 背后的想法是,您可以拥有多个具有相同操作的对象,并且您不必受限于必须实现要对其执行操作的确切具体类型。也就是说,你不需要教它剑/矛/刀,你只需要教它操作,即ISwingableWeapon。如果您想添加新操作,那么是的,您必须编写新代码(显然)。但是,如果您要重用相同的操作,例如在经典的 Rectangle/Circle OCP 解释中使用 CalculateArea(),那么您可以通过使用接口而不是具体类型来使其成为 OCP。
  • 推测是在您的IronFist 类中定义的。 case ISwingableWeapon weapon: weapon.Swing();。铁拳级可以处理它。然后你可以添加任何可摆动的武器,比如ChairLeg,而且你永远不必改变你的职业。

标签: c# switch-statement c#-7.1


【解决方案1】:

我同意你可以滥用它来违反打开/关闭,但你不会像你的例子那样使用它(通过一个接口)。

请考虑反序列化对象的情况。

您可能有一个聊天程序,人们可以发送消息或图像(伪代码)

Object myObject = (object) _network.Read().Deserialize();

if (myObject is IImage) { }
else if (myObject is ITextMessage) { }

您可以改为将其放入switch。它仍然是打开关闭的,因为您可以编写扩展 IImage / ITextMessage 的其他类型。

您必须更改程序的基本功能以支持并非两者兼有的功能。或者你可以有一个 else { doSomeDefaultBehaviour(); }

【讨论】:

  • 该示例根本不是 OCP 违规。具体的 Weapon 类没有被修改 只是为了添加一个新方法。不需要添加额外的类来实现双重调度。事实上,编译器本身现在可以验证类型是否被正确处理。如果没有模式匹配,并且在 C# 8 中,详尽的 switch 表达式,您将不得不依赖运行时检查来执行相同的操作
  • 它与被修改的 Weapon 类没有任何关系,它与接受类型(通常是接口)的代码有关。您不必修改该方法以向您的 switch 语句添加额外的类型,您应该有一个包含它们的接口。虽然这里有一个接口,但它是一个红鲱鱼,因为它实际上并没有被用作接口。
  • 现在您不必这样做了。这根本与 OCP 无关。它是一种函数式编程风格,与 OOP 相比,它对修改的限制更大。类型没有修改,只有方法。最后,您必须编写一些东西来添加新功能。使用函数式编程,这些东西不必在类型本身中。实际上分离关注点要容易得多
  • 我想我无法再进一步解释了。尽管接受了IWeapon,但该类不开放扩展,但您仅限于两种类型,并且如果没有修改,您将无法从中获得任何额外的行为。例如,如果它至少是ISwingableWeapon,那么它将对扩展开放,因为您可以创建IWeapon 的新实现并使用它们无需任何修改 i>.
  • @NibblyPig,再说一次,假设您提供的代码段在黑盒 dll 中,您将如何添加新的数据类型处理?有一些 IDeserializable 接口和一些 PostProcessing 方法不是更好吗?
【解决方案2】:

我同意在您的代码库中使用 switch 语句更难维护并且不能很好地扩展。

但是,如果SwordBow 不依赖于包含Attack 方法的类,那么您别无选择。并且有很多这样的例子,比如序列化(正如 NibblyPig 提到的),还有在构建表示层、ViewModel 等时......

如果SwordBow 不负责处理攻击(这与SRP 相关)并且您有针对这种情况的特定类,那么您将不得不切换不同的类型。您不希望Sword 拥有一种处理序列化的方法和一种处理Sword 在一个特定菜单中的颜色等的方法...

当然,如果您可以通过创建抽象类型的智能接口来避免这种情况,并且只允许您访问所需信息但有时您别无选择,这将很有帮助。

此外,有时语言功能有助于处理遗留代码,但并不代表推荐的良好做法。

【讨论】:

    【解决方案3】:

    A.如果你不能(也就是实际上更容易不)修改你正在使用的类,那么像这样的switch 可能会很有用,例如例外。

    B. switch 中的模式匹配比仅仅区分类型要复杂得多。

    Pattern matching 来自 C# 7.0 中的新功能 展示了这种多功能性。

    public static int SumPositiveNumbers(IEnumerable<object> sequence)
    {
        int sum = 0;
        foreach (var i in sequence)
        {
            switch (i)
            {
                case 0:
                    break;
                case IEnumerable<int> childSequence:
                {
                    foreach(var item in childSequence)
                        sum += (item > 0) ? item : 0;
                    break;
                }
                case int n when n > 0:
                    sum += n;
                    break;
                case null:
                    throw new NullReferenceException("Null found in sequence");
                default:
                    throw new InvalidOperationException("Unrecognized type");
            }
        }
        return sum;
    }
    

    【讨论】:

      猜你喜欢
      • 2021-04-20
      • 2019-09-03
      • 2011-01-14
      • 1970-01-01
      • 2011-01-30
      • 1970-01-01
      • 2015-06-07
      • 2019-11-11
      • 1970-01-01
      相关资源
      最近更新 更多