您的C#已经“起作用”了,随它去吧。

哈Ha!我将为您呈现Igal Tabachnik 撰写的原始文章“您的C#已经可以使用,但只有您允许的情况下”的译文。

前几天,我啾啾一个C#代码段实现FizzBu​​zz使用一些新的C#8.0“功能”。这条推文“风靡一时”,一些人钦佩它的简洁性和功能性,而另一些人问我为什么不使用F#编写它?

自从我上一次用C#编写代码以来,已经过去了4年多的时间,而我通常使用函数式编程这一事实显然已经影响了我今天编写代码的方式。我写的代码片段看起来很整洁自然,但是有些人担心它看起来不像C#代码。
“它看起来太实用了。” 他们写信给我。
根据您询问的人,“函数式编程”对不同的人而言意味着不同的事物。但是,除了讨论语义之外,我还想解释一下为什么该FizzBu​​zz实现似乎可以起作用。

首先,让我们看一下这段代码的作用:

public static void Main(string[] args)
{
      string FizzBuzz(int x) => 
            (x % 3 == 0, x % 5 == 0) switch
            {  
                  (true, true) => "FizzBuzz", 
                  (true, _) => "Fizz", 
                  (_, true) => "Buzz", 
                  _ => x.ToString()
            };
    
      Enumerable.Range(1, 100 ) 
            .Select(FizzBuzz).ToList() 
            .ForEach(Console.WriteLine); 
}

在这里,我们创建一个由lambda表达式表示的本地方法,其结果是使用元组来计算的。

这里的新颖之处在于使用元组(对)来一起计算两个表达式(x%3 = = 0和x%5 = = 0)的结果。这使您可以使用模式匹配来确定最终结果。如果所有选项都不匹配,则默认情况下将返回数字的字符串表示形式。

但是,这段代码中使用的许多“功能”“功能”(包括LINQ风格的foreach循环)都无法使该代码本身发挥功能。使它起作用的原因是,除了将结果输出到控制台外,该程序中使用的所有方法都是表达式。

简而言之,表达是一个总是有答案的问题。就编程而言,表达式是由运行时计算以计算(“返回”)值的常量,变量,运算符和函数的组合。为了说明与语句的区别,让我们为C#程序员编写一个更熟悉的FizzBu​​zz版本:

public static void Main(string[] args) 
{
      foreach( int x in Enumerable.Range(1, 100)) 
      {
             FizzBuzz(x);
      }
} 

public static void FizzBuzz( int x) 
{
      if (x % 3 == 0  && x % 5 == 0) 
             Console.WriteLine("FizzBuzz"); 
      else if (x % 3 == 0 ) 
             Console.WriteLine("Fizz"); 
      else if (x % 5 == 0 ) 
             Console.WriteLine("Buzz"); 
      else
             Console.WriteLine(x);
}

自然,这远不是一种“干净的”代码,可以对其进行改进,但是人们不得不同意这是普通的C#代码。通过仔细检查FizzBu​​zz方法,即使考虑到其简单性,它也清楚地显示出一些设计问题。

首先,该程序违反了SOLID的第一个原则-单一责任原则。它混合了根据数字计算输出值并将此值输出到控制台的逻辑。结果,它违反了依赖倒置的原理(SOLID的最后一个),将结果的输出紧密地连接到控制台。最后,该程序的这种实现方式很难重用和沙箱代码。当然,对于这样那样的一个简单的程序,这是没有意义的进入设计的复杂性,否则像可能会变成

通过分离值的接收并将其输出到控制台,可以解决所有上述问题。即使不使用复杂的语言构造,只需将结果值返回给调用代码也可以减轻我们使用此值的责任。

public static string FizzBuzz(int x)
{
      if (x % 3 == 0 && x % 5 == 0)
            return "FizzBuzz";
      else if (x % 3 == 0)
            return "Fizz";
      else if (x % 5 == 0)
            return "Buzz";
      else
            return x.ToString();
}

当然,这些并不是根本性的改变,但这已经足够了:

  1. FizzBu​​zz方法现在是一个表达式,该表达式接受一个数值并返回一个字符串。
  2. 它没有其他责任和隐患,这使其变成了纯粹的功能。
  3. 它可以独立使用和测试,而无需任何其他依赖关系和设置。
  4. 调用此函数的代码可以自由地对结果做任何事情-现在这不是我们的责任。

这就是函数式编程的本质-程序由产生任何值的表达式组成,并将这些值返回给调用代码。通常,这些表达式是完全独立的,并且它们的调用结果仅取决于输入数据。在最顶端,在入口点(有时也称为``世界末日''),函数返回的值收集并与世界其他地方进行交互。在面向对象的术语中,有时将其称为“洋葱体系结构”(或“端口和适配器”)-由业务逻辑和负责与外界交互的命令外壳组成的干净核心。

所有编程语言在某种程度上都使用表达式代替语句。随着时间的推移,C#不断发展壮大,以引入使表达式更易于使用的功能:LINQ,基于表达式的方法,模式匹配等等。这些``功能''通常被称为``功能性'',因为它们存在-在F#或Haskell之类的语言中,几乎不可能存在除表达式之外的任何东西。

实际上,C#团队现在鼓励这种编程风格。在最近的NDC伦敦会议上,比尔·瓦格纳(Bill Wagner)敦促开发人员改变其(必要的)习惯并采用现代方法:


可以在功能上使用C#(以及其他命令式语言,例如Java),但这需要很多工作。这些语言使功能样式成为例外,而不是规范。我鼓励您学习函数式编程语言以成为一流的专家。

All Articles